Topic: Global Init/Shutdown: A solution (hopefully)


Author: herbs@interlog.com (Herb Sutter)
Date: 1995/11/14
Raw View
In article <4873sc$hum@gabi.gabi-soft.fr>,
   kanze@gabi-soft.fr (J. Kanze) wrote:
>Another interesting case:
>
>    A                   a1 ;
>    A                   a2 ;
>    A                   a3 ;
>    //  ...
>
>The constructor of a2 throws an exception (not caught, of course).  In
>no case can the compiler be allowed to call the destructor to a3.  All

Or a2 for that matter, at least not according to C++PL2 sec 9.4.1 par 2.

>of the compilers I've tested do.  Which in practice means that you
>cannot safely let an exception escape from a constructor unless you can
>guarantee that the type can never be used as a static variable with file
>scope.  (Gee, and I always thought that one of the main reasons for
>using exceptions was error reporting from constructors.)

Trying the above under BC++ 4.52, A::A() is called twice and then A::~A() is
called once, which is correct.  Just out of curiosity, what compilers acted
improperly?  Pls. email the answer if this is drifting off-topic for c.s.c++.
 Thanks.

 [Moderator's note: personally I'd be inclined to say that discussion
 of which compilers are standard-conforming in some particular respect
 is appropriate for comp.std.c++.  If anyone has any comments or
 suggestions about the moderation policy, please send them to
 std-c++-request@ncar.ucar.edu.  -fjh.]

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Herb Sutter                 2228 Urwin, Ste 102         voice (416) 618-0184
Connected Object Solutions  Oakville ON Canada L6L 2T2    fax (905) 847-6019
---
[ comp.std.c++ is moderated.  Submission address: std-c++@ncar.ucar.edu.
  Contact address: std-c++-request@ncar.ucar.edu.  The moderation policy
  is summarized in http://dogbert.lbl.gov/~matt/std-c++/policy.html. ]





Author: James Kanze US/ESC 60/3/141 #40763 <kanze@lts.sel.alcatel.de>
Date: 1995/11/14
Raw View
In article <488tdp$1tu@steel.interlog.com> herbs@interlog.com (Herb
Sutter) writes:

|> In article <4873sc$hum@gabi.gabi-soft.fr>,
|>    kanze@gabi-soft.fr (J. Kanze) wrote:
|> >Another interesting case:
|> >
|> >    A                   a1 ;
|> >    A                   a2 ;
|> >    A                   a3 ;
|> >    //  ...
|> >
|> >The constructor of a2 throws an exception (not caught, of course).  In
|> >no case can the compiler be allowed to call the destructor to a3.  All

|> Or a2 for that matter, at least not according to C++PL2 sec 9.4.1 par 2.

Correct.

|> >of the compilers I've tested do.  Which in practice means that you
|> >cannot safely let an exception escape from a constructor unless you can
|> >guarantee that the type can never be used as a static variable with file
|> >scope.  (Gee, and I always thought that one of the main reasons for
|> >using exceptions was error reporting from constructors.)

|> Trying the above under BC++ 4.52, A::A() is called twice and then A::~A() is
|> called once, which is correct.  Just out of curiosity, what compilers acted
|> improperly?  Pls. email the answer if this is drifting off-topic for c.s.c++.
|>  Thanks.

Since the moderator seems to think that it is on topic:-):

The only compiler I have access to that supports exceptions is Sun CC
4.0.1; this is the compiler I used for my initial tests.  I modified
the test to call exit, instead of throwing an exception, and got the
following results:

                   ctors   dtors
 Sun CC 4.0.1 (w/ exceptions)   2 3
 Sun CC 2.1     2       0
 Cfront 3.0.1 (direct port, not from Sun) 2 3
 g++ 2.6.3     2 0
 g++ 2.7.0     2 0

None of the compilers on Sun get it right; global constructors seem to
be an all or nothing thing for them.

--
James Kanze         Tel.: (+33) 88 14 49 00        email: kanze@gabi-soft.fr
GABI Software, Sarl., 8 rue des Francs-Bourgeois, F-67000 Strasbourg, France
Conseils,    tudes et r   alisations en logiciel orient    objet --
                -- A la recherche d'une activit    dans une region francophone


---
[ comp.std.c++ is moderated.  Submission address: std-c++@ncar.ucar.edu.
  Contact address: std-c++-request@ncar.ucar.edu.  The moderation policy
  is summarized in http://dogbert.lbl.gov/~matt/std-c++/policy.html. ]





Author: kanze@gabi-soft.fr (J. Kanze)
Date: 1995/11/17
Raw View
Herb Sutter (herbs@interlog.com) wrote:
|> In article <9511141105.AA01230@lts.sel.alcatel.de>,
|>    James Kanze US/ESC 60/3/141 #40763 <kanze@lts.sel.alcatel.de> wrote:
|> >|> >Another interesting case:
|> >|> >
|> >|> >    A                   a1 ;
|> >|> >    A                   a2 ;
|> >|> >    A                   a3 ;
|> >|> >    //  ...
|> >|> >
|> >|> >The constructor of a2 throws an exception
|> >|>
|> >|> Trying the above under BC++ 4.52, A::A() is called twice and then
|> >|> A::~A() is called once, which is correct.
|> >
|> >The only compiler I have access to that supports exceptions is Sun CC
|> >4.0.1; this is the compiler I used for my initial tests.  I modified
|> >the test to call exit, instead of throwing an exception, and got the
|> >following results:
|> >
|> >                                  ctors   dtors
|> >    Sun CC 4.0.1 (w/ exceptions)            2   3
|> >    Sun CC 2.1                  2       0
|> >    Cfront 3.0.1 (direct port, not from Sun)    2   3
|> >    g++ 2.6.3                   2   0
|> >    g++ 2.7.0                   2   0

|> Hold on... you changed the question! :-)  If I modify my BC++ 4.52 program

I had to.  The Cfront based compilers and g++ don't support exceptions!

|> to call exit() (instead of throwing) in a2's ctor, I get the incorrect
|> result of  three A::~A() calls.  If I change it to call abort() in a2's
|> ctor, I get zero A::~A()s.

|> Does the WP specify what/how dtors should be called in the case of an
|> exit() or abort()?

Yes and no.  In the case of abort, it is explicit: no destructors are
called.  In the case of exit...  I can find no statement forbidding
calling exit from a constructor, or saying that it is undefined
behavior.  So one must suppose that it is permitted.  But calling a
destructor on an uninitialized object will forceably result in undefined
behavior.  Whence I conclude that an implementation may not generate
code which does so *unless* my program already contains undefined
behavior.
--
James Kanze           (+33) 88 14 49 00          email: kanze@gabi-soft.fr
GABI Software, Sarl., 8 rue des Francs Bourgeois, 67000 Strasbourg, France
Conseils, itudes et rialisations en logiciel orienti objet --
              -- A la recherche d'une activiti dans une region francophone
---
[ comp.std.c++ is moderated.  Submission address: std-c++@ncar.ucar.edu.
  Contact address: std-c++-request@ncar.ucar.edu.  The moderation policy
  is summarized in http://dogbert.lbl.gov/~matt/std-c++/policy.html. ]





Author: "Eugene Radchenko" <eugene@qsar.chem.msu.su>
Date: 1995/11/08
Raw View
Hi people!
I have got a few ideas concerning the static init/shutdown problem which I
would like to see discussed and criticized here.

The major problem is to ensure the right order of initialization (and
the inverse right order of destruction ;) between the translation units.
I think it can be solved by requiring that this initialization
really happens 'just' before the objects/functions in it are first accessed
- which may even happen before main() - when exactly is unimportant. If the
hardware architecture & OS support virtual memory (and now that Win95 is
out it means any general-purpose target platform worth programming for), it
can even be implemented in a way imposing no overhead once the program is
fully initialized. If a unit is not referenced, it would not be
constructed. Maybe it is really a desirable behavior (although draft
standard says otherwise ;). Alternatively, implementation could check for
the remaining uninitialized units and construct them just before caklling
main().

Otherwise (e.g. in embedded systems) we are probably limited to the
functions only, where the overhead ir equivalent to that required by the
static local objects.

Also, I think that the lifetime of the local static objects and global
translation units (i.e. sets of global objects defined in them) should
conform to the single stack-like semantics (actual implementation data
structure may vary). In other words, the object/unit is 'pushed' when its
construction starts (or ends). At the implementation-dependent moment after
main() returns, the implementation shutdown code starts 'popping'
objects/units from the stack and calling the appropriate destructors. This
way we guarantee that each object is destroyed in the environment at least
as rich as the one it was constructed in. (To observe this more completely,
it might be reasonable to push local statics between the objects from a
single unit. This increases the support memory requirements, though).

A fine point here is the local statics constructed during the
construction/destruction of other objects. Both ways of defining the
lifetime start (construction start/completion) seem wrong here. I don't
know which one is less wrong (especially if we work at the unit-as-a-whole
granularity).

One benefit of this approach is that it does not break the existing code.
Programs that did not use the interdependent statics will work as before,
while the game rules for others would be more clear and helpful.

                Best regards                       Genie

--
--------------------------------------------------------------------
Eugene V. Radchenko           Graduate Student in Computer Chemistry
E-mail: eugene@qsar.chem.msu.su                Fax: +7-(095)939-0290
Ordinary mail:  Chair of Organic Chemistry, Department of Chemistry,
                      Moscow State University, 119899 Moscow, Russia
*****************  Disappearances are deceptive  *******************


---
[ comp.std.c++ is moderated.  Submission address: std-c++@ncar.ucar.edu.
  Contact address: std-c++-request@ncar.ucar.edu.  The moderation policy
  is summarized in http://dogbert.lbl.gov/~matt/std-c++/policy.html. ]





Author: rac@intrigue.com (Robert Coie)
Date: 1995/11/10
Raw View
In article <AI-HDemqX1@qsar.chem.msu.su>, "Eugene Radchenko"
<eugene@qsar.chem.msu.su> wrote:

: Hi people!
: I have got a few ideas concerning the static init/shutdown problem which I
: would like to see discussed and criticized here.
:
: The major problem is to ensure the right order of initialization (and
: the inverse right order of destruction ;) between the translation units.
: I think it can be solved by requiring that this initialization
: really happens 'just' before the objects/functions in it are first accessed
: - which may even happen before main() - when exactly is unimportant.
[snip]

I don't see how this can be guaranteed.  The simplest pathological case I
can think of (although trivial) is:

struct A { A(); };
A stA;
A::A() {}

I can't see how you can guarantee that the initialization of stA occurs
before the call to the constructor used to initialize it.  The burden on
insuring that static objects are properly constructed before they are
referenced must fall, in the end, on the programmer, as far as I can see.

Robert Coie
Implementor, Intrigue Corporation
rac@intrigue.com
---
[ comp.std.c++ is moderated.  Submission address: std-c++@ncar.ucar.edu.
  Contact address: std-c++-request@ncar.ucar.edu.  The moderation policy
  is summarized in http://dogbert.lbl.gov/~matt/std-c++/policy.html. ]





Author: JdeBP@jba.co.uk
Date: 1995/11/11
Raw View
Eugene Radchenko (eugene@qsar.chem.msu.su) wrote:
: I think it can be solved by requiring that this initialization
: really happens 'just' before the objects/functions in it are first accessed
: - which may even happen before main() - when exactly is unimportant. If the
: hardware architecture & OS support virtual memory [...]

I really don't think that the thousands of programmers who daily program
for operating systems that do not support virtual memory will happily let
ISO get away with defining the C++ language so that a conforming
implementation is not possible on their machines.

Incidentally, if what you are suggesting is what I think that you are
suggesting, you are imposing an implementation overhead that most people
will find unacceptable, since every object with static storage duration
would have to reside in a separate page.

That is unless you were going for translation-unit granularity, in which
case the "Deadly Embrace Initialisation", where the initialisation of
object A in translation unit X references object B in translation unit Y
and the initialisation of object C in translation unit Y references object
D in translation unit X, becomes a problem once more (which translation
unit do you initialise first ?) and you haven't solved the global
initialisation problem at all.

:                    If a unit is not referenced, it would not be
: constructed. Maybe it is really a desirable behavior (although draft
: standard says otherwise ;).

I don't want any such behaviour.  I rely on static duration instances of
single-instance classes to model libraries in DLLs.  The objects are not
themselves referenced, but the constructors and destructors perform library
initialisation and cleanup.  I usually put the instances in their own
translation unit, along with _DLL_InitTerm(), as well.  Your proposed
alteration would break my programs, and, worse, leave me with no practical
alternative to achieve the same ends.

: Also, I think that the lifetime of the local static objects and global
: translation units (i.e. sets of global objects defined in them) should
: conform to the single stack-like semantics (actual implementation data
: structure may vary). In other words, the object/unit is 'pushed' when its
: construction starts (or ends). At the implementation-dependent moment after
: main() returns, the implementation shutdown code starts 'popping'
: objects/units from the stack and calling the appropriate destructors.

Although not implemented as a stack, you will find that many
implementations already do this, because one favourite means of
implementing global initialisation is to construct at link time a list of
all static duration objectsi from all translation units and bind them into
the program.  At runtime this list is traversed from one end to the other
to perform dynamic initialisation, and in the reverse direction to perform
destruction.

Of course, a stack allows you to defer the ordering of the elements in the
list to run-time.

The main problem with global initialisation is that it is very hard to
describe in English the order in which the objects in a program should be
initialised, especially when individual translation units cannot know how
many other translation units will comprise the whole program, and what
static duration objects they will require.  So it's doubly hard to describe
it in a computer language.

The insight into implementation above may reveal a direction to pursue,
however.  If the total number of static duration objects in the whole
program is only known at link time, then the mechanism for controlling the
relative ordering amongst translation units belongs with the linker.

( Of course the C++ Working Paper has only a peripheral notion of a linker,
  and no way to control it at all.  I suspect that you'd have a hard time
  persuading people to come up with a standard mechanism for communicating
  directives to the linker from within the C++ language.  )


---
[ comp.std.c++ is moderated.  Submission address: std-c++@ncar.ucar.edu.
  Contact address: std-c++-request@ncar.ucar.edu.  The moderation policy
  is summarized in http://dogbert.lbl.gov/~matt/std-c++/policy.html. ]





Author: JdeBP@jba.co.uk
Date: 1995/11/11
Raw View
Further to the global initialisation/cleanup discussion, can anyone tell me
when `woggle::~woggle' is called in the following program ?  Is undefined
behaviour invoked ?

If you think that the program is non-conforming (aside from any
typographical mistakes that I make, which I'm sure that you'll overlook in
view of the latesness of the hour, and the fact that I'm typing this in by
hand) please explain where and why it is non-conforming.

//
//  **************************************************************
// WOGGLE     -- Jonathan de Boyne Pollard  19951111
//  **************************************************************
//

#include <stdlib.h>

class woggle {
public:
 char * p ;
 woggle() p(new(nothrow()) char) {}
 ~woggle() { char * q = p ; p = 0 ; delete q ; }
} ;

void *
std::operator new ( size_t s, const std::nothrow & ) throw()
{
 return malloc(s) ;
}

void
std::operator delete ( void * p ) throw()
{
 static woggle w ;
 *w.p = ' ' ;
 free(p) ;
}

int
main ( int, char ** )
{
 delete new(nothrow()) char ;
 return EXIT_SUCCESS ;
}
---
[ comp.std.c++ is moderated.  Submission address: std-c++@ncar.ucar.edu.
  Contact address: std-c++-request@ncar.ucar.edu.  The moderation policy
  is summarized in http://dogbert.lbl.gov/~matt/std-c++/policy.html. ]





Author: fjh@munta.cs.mu.OZ.AU (Fergus Henderson)
Date: 1995/11/11
Raw View
JdeBP@jba.co.uk writes:

>Further to the global initialisation/cleanup discussion, can anyone tell me
>when `woggle::~woggle' is called in the following program ?  Is undefined
>behaviour invoked ?

>#include <stdlib.h>
>
>class woggle {
>public:
> char * p ;
> woggle() p(new(nothrow()) char) {}
> ~woggle() { char * q = p ; p = 0 ; delete q ; }
>} ;
>
>void *
>std::operator new ( size_t s, const std::nothrow & ) throw()
>{
> return malloc(s) ;
>}

As discussed here recently, `operator new' should be defined in the
global namespace, as specified by 3.7.3.  Your program violates
17.3.3.1/1 "a program shall not extend the namespace `std'".

>void
>std::operator delete ( void * p ) throw()
>{
> static woggle w ;
> *w.p = ' ' ;
> free(p) ;
>}

Even if this were defined in the global namespace, there would
still be a problem.  You didn't replace `operator new(size_t)', so
there is no guarantee that objects passed to your replacement for
`operator delete' will have been allocated with `malloc()'.

>int
>main ( int, char ** )
>{
> delete new(nothrow()) char ;
> return EXIT_SUCCESS ;
>}

If we ignore the two problem noted above, there is still at least
one more problem.

Lets see... there are no user-defined global objects or static members
with constructors, so after execution of the constructors for library
objects such as `cin' and `cout', execution begins in main().
First we execute `new(nothrow()) char', which invokes `operator new (1,
nothrow())', which returns the result of `malloc(1)'.
Then we execute `delete' on the resulting `char *' pointer,
which invokes your `operator delete(void *)'.
One entry to your `operator delete', the constructor for `static woggle w'
gets executed.  This initializes `w.p' with `new (nothrow()) char',
which as before returns the result of `malloc(1)'.
Then the code

 *w.p = ' ' ;
 free(p) ;

is executed to complete the execution of `operator delete'.
Finally, on exit from main(), the desctructor for `w' will be called.
Oops, undefined behaviour:

 ~woggle() { char * q = p ; p = 0 ; delete q ; }
      ^^^^^
Here `w.p' has already been deallocated, so any attempt to even
just copy the invalidated pointer it is undefined behaviour.

--
Fergus Henderson              WWW: http://www.cs.mu.oz.au/~fjh
fjh@cs.mu.oz.au               PGP: finger fjh@128.250.37.3
---
[ comp.std.c++ is moderated.  Submission address: std-c++@ncar.ucar.edu.
  Contact address: std-c++-request@ncar.ucar.edu.  The moderation policy
  is summarized in http://dogbert.lbl.gov/~matt/std-c++/policy.html. ]





Author: kanze@gabi-soft.fr (J. Kanze)
Date: 1995/11/13
Raw View
JdeBP@jba.co.uk wrote:

|> : Also, I think that the lifetime of the local static objects and global
|> : translation units (i.e. sets of global objects defined in them) should
|> : conform to the single stack-like semantics (actual implementation data
|> : structure may vary). In other words, the object/unit is 'pushed' when its
|> : construction starts (or ends). At the implementation-dependent moment
|> : after main() returns, the implementation shutdown code starts 'popping'
|> : objects/units from the stack and calling the appropriate destructors.

|> Although not implemented as a stack, you will find that many
|> implementations already do this, because one favourite means of
|> implementing global initialisation is to construct at link time a list of
|> all static duration objectsi from all translation units and bind them into
|> the program.  At runtime this list is traversed from one end to the other
|> to perform dynamic initialisation, and in the reverse direction to perform
|> destruction.

This is, in effect, what most implementations do.  By my reading of the
standard, however, it is *not* legal.  Given the following program (in
which all types with capital letters are classes with constructors and
destructors):

    extern int          f() ;
    A                   a1 ;
    int                 a2 = f() ;
    A                   a3 ;

    int
    f()
    {
        static A            a2a ;
        return 0 ;
    }

    int
    main()
    {
        return 0 ;
    }

According to 3.6.2, the static objects will be initialized in the order
a1, a2/a2a, a3.  (The initialization of a2 encompasses the
initialization of a2a.)  Thus, applying the rule from 3.6.3:
``Destruction [of static objects] is done in reverse order of
initialization'', the destructors must be called in the reverse order:
a3, a2a, a1.  In practice, this requires some sort of stack-like list,
since most compilers will not do enough flow analysis to determine the
exact order.  (In fact, I do not believe they can.  The static variable
in f() could be in a block goverened by an if whose condition depended
on the result of a call to getenv, for example.)

Note that in 3.6.3, the rule very definitly applies to ``static
objects''.  Given that in the preceding section (3.6.2), the notation of
``nonlocal static objects'' occurs consistently when local static
objects are not considered, I do not think one can attribute this to
carelessness on the part of the committee; IMHO, the intent is clear.

Another interesting case:

    A                   a1 ;
    A                   a2 ;
    A                   a3 ;
    //  ...

The constructor of a2 throws an exception (not caught, of course).  In
no case can the compiler be allowed to call the destructor to a3.  All
of the compilers I've tested do.  Which in practice means that you
cannot safely let an exception escape from a constructor unless you can
guarantee that the type can never be used as a static variable with file
scope.  (Gee, and I always thought that one of the main reasons for
using exceptions was error reporting from constructors.)
--
James Kanze           (+33) 88 14 49 00          email: kanze@gabi-soft.fr
GABI Software, Sarl., 8 rue des Francs Bourgeois, 67000 Strasbourg, France
Conseils, itudes et rialisations en logiciel orienti objet --
              -- A la recherche d'une activiti dans une region francophone
---
[ comp.std.c++ is moderated.  Submission address: std-c++@ncar.ucar.edu.
  Contact address: std-c++-request@ncar.ucar.edu.  The moderation policy
  is summarized in http://dogbert.lbl.gov/~matt/std-c++/policy.html. ]