ISO/ IEC JTC1/SC22/WG21 N0802

 
Accredited Standards Committee X3       Doc No: X3J16/95-0202   WG21/N0802
Information Processing Systems          Date:   November 3, 1995
Operating under the procedures of       Project: Programming Language C++
American National Standards Institute   Ref Doc:
                                        Reply to: Josee Lajoie
                                                  (josee@vnet.ibm.com)
 
Start and Termination Issues and Proposed Resolutions
=====================================================
 
551 - Must all C++ programs define main?
 
  3.6.1[basic.start.main] paragraph 1 says:
    "A program shall contain a global function called main, which is
     the designated start of the program."
 
    In C, a freestanding environment is not required to have a main
    function; the program invocation is implementation-defined.  Should
    this be the case in C++?
 
  Proposed Resolution:
    Neal Gafter answers this question in an editorial box in 3.6.1.
    His answer should be incorporated into the WP as a note.  And
    this should be added as an item to appendix C (C compatibility).
 
  Add to 3.6.1[basic.start.main] paragraph 1, after the sentence above:
    "[Note: a C++ freestanding environment is required to have a main
      function to guarantee that the semantics of program start and
      termination (that is, the construction and destruction of objects
      with static storage duration) behave as defined in this
      International Standard]."
 
UK 38 - Should the standard say that exit's argument is returned to the
        program's environment?
 
  3.6.1[basic.start.main] paragraph 4 says:
    "Calling the function [exit] declared in <cstdlib> terminates the
     program... The argument value is returned to the program's
     environment as the value of the program."
 
    This last sentence cannot be a requirement on a C++ program.
 
  Proposed Resolution:
    Delete this last sentence.
    The description of exit's behavior belongs in the library clauses
    (see 18.3 [lib.support.start.term]).
 
462 - calling exit from a destructor for a global variable
 
  3.6.1[basic.start.main] paragraph 4 says:
    "Calling the function [exit] declared in <cstdlib> terminates the
     program ..."
  3.6.3[basic.start.term] paragraph 1 says:
    "Destructors for initialized static objects are called when ...
     calling exit()."
 
    What happens if exit is called from the destructor for an object
    with static storage duration?  The destructor will be called again
    for the same object when exit destroys objects with static storage
    duration.
 
    What if a destructor for an object with static storage duration
    throws an exception and the user-defined terminate routine calls
    exit?  The destructor will be called again for the same object when
    exit destroys objects with static storage duration.
 
  Proposed Resolution:
    Calling 'exit' (directly or indirectly) from a destructor called to
    destroy an object with static storage duration results in undefined
    behavior.
 
    Add to 3.6.1[basic.start.main] paragraph 4:
      "If 'exit' is called to end a program during the destruction of
       an object of namespace scope with static storage duration, the
       program has undefined behavior."
 
552 - Why is an implementation prohibited from destroying objects
      with automatic storage duration when exit is called?
 
  3.6.1[basic.start.main] paragraph 4 says:
    "Calling the function [exit] declared in <cstdlib> terminates the
     program without leaving the current block and hence without
     destroying any objects with automatic storage duration.
 
    Box 15 says:
    "Several members of the Core editing group question this. Can we
     require that exit destroy automatic-duration objects? Or make it
     implementation defined whether the unwinding occurs? If an
     implementer's market wants exit to unwind, and the implementer
     can provide the necessary stack-unwinding, why should the Standard
     prohibit it?  For an immediate "get me out of here", there is
     always the abort function.  The footnote in 18.3 claims that an
     unwinding can be achieved by throwing an exception that is caught
     in main, but this assumes and requires that no handlers for ...
     intervene.  There appears to be no way of exiting via an exception
     that cannot be intercepted by such handlers."
 
  Mike Ball had the following comments on the proposal from editorial
  box 15:
    > There are reasons to call exit rather than abort even in
    > "emergency" cases.  For example you might want to execute static
    > destructors to empty file buffers.  You might want to execute a
    > return with a different status, but not dump core.  If you make
    > this change you will be deleting a behavior on which some programs
    > depend and providing no alternative way to get the behavior.
    >
    > I see no justification at all for such a drastic change in
    > well-defined semantics at this late stage.
 
  I believe Mike's arguments against the proposal in editorial box 15
  are important enough that we should leave the language as is.
 
  Proposed Resolution:
    Keep paragraph 4 as is (and delete the editorial box).
 
527 - When must the initialization of nonlocal objects take place?
 
  3.6.2[basic.start.init] paragraph 1 says:
    "The initialization of nonlocal objects with static storage duration
     defined in a translation unit shall be done before the first use
     of any function or object defined in that translation unit.  Such
     initializations can be done before the first statement of main()
     or deferred to any point in time before the first use of a function
     or object defined in that translation unit."
 
  Some have complained that it should be required that nonlocal objects
  with static storage duration be initialized before main is entered.
 
  Jerry Schwarz would like a guarantee that the following program will
  work:
    > // myfile.C
    > #include <goodlib.h>
    > Info myinfo(.....) ;
    >
    > The idea is that you don't need to have a complete registry in
    > advance of all Info's.  The file might contain lots of data and
    > code but none of it will be used unless myinfo is registered and
    > it is the constructor for Info that registers myinfo.  Since there
    > isn't any function in this file that is ever called spontaneously
    > the words as proposed allow this file to be completely ignored.
 
  Mike Ball answered Jerry as follows:
    > As far as I can tell the words of the standard allow this file to
    > be completely ignored on other grounds as well.  For instance, if
    > this is in a library, it may not be loaded at all, since there is
    > no reference to any name in it.
 
  Given that the Standard imposes no requirement on which files
  constitute a program, and given the current technology for dynamically
  loaded libraries, requiring that every objects with static storage
  duration defined in namespace scope be initialized before main is
  entered creates some "implementation problems" and I don't believe the
  Standard can impose more than what is currently imposed.
 
  Proposed Resolution:
    The paragraph above should be clarified so that it is clear that
    "shall be done before the first use" refers to a use after main has
    been invoked:
     "It is implementation-defined (unspecified?) whether the
      initialization of an object with static storage duration is done
      before the first statement of main() or deferred to any point in
      time before the first use (by the function main() or by any
      function called directly or indirectly by main()) of a function
      or object defined in the same translation unit."
      [Example:
        // -- File 1 --
          #include "a.h"
          #include "b.h"
 
          B b;
 
          A::A() {
          {
             b.Use();
          }
 
        // -- File 2 --
          #include "a.h"
 
          A a;
          main() { ... }
 
      it is implementation-defined (unspecified?) whether 'a' is
      defined before main is entered or whether its definition is
      delayed until 'a' is first used by the program.  It is
      implementation-defined (unspecified?) whether 'b' is defined
      before main is entered or whether this definition is delayed until
      it is used by A::A().
      -- end example]"
 
  Jerry Schwarz wanted the Core WG to consider the following proposal:
    > add non-normative text (i.e. a footnote) explaining that it is
    > desirable for implementations to do better [than initialize after
    > main is entered] whenever possible.
 
555 - Clarify: order of (static vs dynamic) initialization
 
  3.6.2[basic.start.init] paragraph 1 says:
    "Objects with static storage duration initialized with constant
     expressions are initialized before any dynamic initialization
     takes place. The order of initialization of nonlocal objects with
     static storage duration defined in the same translation unit is the
     order in which their definition appears in the translation unit."
 
  ANSI public comment 8.2:
    "I find this paragraph confusing.  To me it appears as if the
     statement that objects "initialized with constant expressions
     are initialized before any dynamic ...  initialization takes place"
     is in conflict with the statement that within a translation unit,
     "the order of initialization of non-local objects ...  is the
     order in which their definition appears"."
 
  [John Skaller also notes in core-6262:]
    > Consider:
    >
    >   complex z = 1;
    >
    > Well?  '1' a constant expression.  But clearly, this is a dynamic
    > initialisation.  So the wording is contradictory.  In this case a
    > constructor is involved so the answer is obvious.
 
  Proposed Resolution:
  Rewrite the above as follows:
    "Objects of POD types (3.9) with static storage duration
     initialized with constant expressions are initialized before any
     dynamic initialization takes place.  Namespace scope objects with
     static storage duration defined in the same translation unit and
     dynamically initialized are initialized in the order in which their
     definition appears in the translation unit."
 
  Note:
    The WP already indicates that the implicitly initialization to 0 is
    done before any static or dynamic initialization takes place.  The
    sentence before the two sentences quoted above says:
      "The storage for objects with static storage duration is
       zero-initialized (8.5) before any other initialization takes
       place."
 
    What constitute static and dynamic initialization if an aggregate
    is initialized with a brace enclosed initializer-list is mostly
    taken care of in 8.5.1[dcl.init.aggr] paragraph 13:
      "When an aggregate is initialized with a brace enclosed
       initializer-list, if some members are initialized with constant
       expressions and other with non-constant expressions, it is
       unspecified whether the initialization of members with constant
       expressions takes place during the static phase or during the
       dynamic phase of initialization."
 
    Proposed Resolution:
    I believe this should be augmented to say when initialization with
    a brace enclosed initializer-list is performed during the static
    phase of initialization.
      "When an aggregate is initialized with a brace enclosed
       initializer-list, if all members are initialized with constant
       expressions, the initialization of all members takes place during
       the static phase of initialization; otherwise, if some members
       are initialized with constant expressions and other with
       non-constant expressions, it is unspecified whether the
       initialization of members with constant expressions takes place
       during the static phase or during the dynamic phase of
       initialization."
 
429 - Order of initialization of reference variables
 
  Is the following initialization of "r" performed "statically" (that
  is, at link time) or "dynamicaly" (that is, at its turn among
  dynamically initialized nonlocal objects in the translation unit)?
 
    extern int x;
    int &r = x;
 
  Proposed Resolution:
    This reference initialization takes place during the static
    initialization phase.  To allow this, another category of "constant
    expressions" is needed in 5.19[expr.const].
 
    Add to 5.19:
      "A 'reference constant expression' is an lvalue designating an
       object of static storage duration or a function.  The
       subscripting '[]' operator, the class member access '.' and '->'
       operators, the '&' and * unary operators, and reference casts
       (except those invoking user-defined conversion functions (12.3)
       and except dynamic_casts (5.2.6)) can be used by the lvalue
       expression of a reference constant expression, but the value of
       an object shall not be accessed by the use of these operators.
       An lvalue expression that designates a member or base class of a
       non-POD class object (9) is not a reference constant expression
       (12.7).  Function calls shall not be used in a reference constant
       expression, even if the function is inline and has a reference
       return type."
 
430 - Order of destruction of local static variables
 
  3.6.3 says static objects are destroy in reverse order of
    initialization.
  6.7 says destruction order is unspecified.
  Which one is right?
 
    The possible solutions are:
    1) The order of destruction of local objects with static storage
       duration is unspecified [as 6.7 implies].
    2) It is guaranteed that local objects with static storage duration
       defined in the same function are destroyed in reverse order of
       construction.  The order of destruction of local objects with
       static storage duration is otherwise unspecified.
    3) It is guaranteed that objects with static storage duration
       declared in the same translation unit (either defined in block
       scope or declared in namespace scope) are destroyed in reverse of
       construction.  The order of destruction of variables with static
       storage duration is otherwise unspecified.
    4) It is guaranteed that all variables with static storage duration
       in the program (either declared at block scope or declared in
       namespace scope) are destroyed in reverse of construction [as
       3.6.3 implies].
 
   The choice of which solution to adopt will depend on how one views
   the constitution of C++ programs.  One item to consider is how one
   views shared libraries: either they are an integral part of the
   program or each library is a program on its own.  Another item to
   consider is how strong one views the inter-relationship between local
   static objects (within the same file or within the same function).
 
   Solution 4) is pleasing to those who believe that there is a strong
   interrelationship between shared libraries and the rest of the
   program, between local statics and the rest of the objects with
   static storage duration.  Solution 1), 2) or 3) is pleasing to those
   who believe shared libraries are programs on their own.  Solution
   1), is pleasing to those who believe local statics are not closely
   related with other objects of static storage duration.
 
   [Mike Ball:]
     I, and a lot of other people, like to think of a library itself as
     a self-contained object.  Any static constructors within that
     library are for the use of that library only.  There are no
     references in or out of the library except for temporary ones
     involving parameters.  This implies, for example, that one could
     load a library when needed and get rid of it when it's not needed.
 
   [Jerry Schwarz:]
     I think this is a red herring because if the system makes
     provision for dynamic loading and unloading of libraries then the
     standard really doesn't say anything about
     initialization/destruction of their statics.  I don't have any
     problem (and I don't think it creates any conformance issue) if the
     statics of an "unloadable" library are linked on a separate chain.
 
   [Mike Ball:]
     I don't like creating global chains of unrelated objects for any
     reason, and I think that the majority of local statics are totally
     unrelated, both to one another and to file scope statics.
 
     I think that the scheme for controlling destruction of local
     statics is an additional overhead that is of no use whatsoever to
     most programmers, of small value to a few and with a small
     disadvantage for a different few.
 
     Every global data structure is one more thing to be locked and
     unlocked against concurrent access and one more thing that requires
     allocating unknown amounts of storage.  I believe that cases where
     the order of destruction is important are a distinct and small
     minority of the total uses, and that adding such a data structure
     is not doing the rest of the programmers any favor at all.
 
   Proposed Resolution:
    either 1) or 3).
    The choice over 1) and 3) seems to be a matter of programming
    style.  We will probably need to wrestle with these two options a
    little to decide which one is most appropriate for the standard.
 
    Because solution 4) is an expensive option for implementations, I
    don't believe it is a viable solution.
 
484 - When must objects of static storage duration be destroyed wrt to
      calls to functions registered with atexit?
 
  3.6.3[basic.start.term] paragraph 1 says:
    "If atexit is to be called, the implementation shall not destroy
     objects initialized before an atexit call until after the function
     specified in the atexit() call has been called."
 
  6.7[stmt.dcl] paragraph 5 says:
    "The destructor [for local object with static storage duration] is
     called either immediately before or as a part of the calls to the
     atexit functions."
 
  18.3[lib.support.start.term] paragraph 3 says:
    "-- First all functions f registered by calling atexit(f) are
        called, in reverse order of their registration.
     -- Next, all static objects are destroyed in the reverse order of
        their construction. ..."
 
    When must local objects with static storage duration be destroyed?
 
    Jerry Schwarz [core-5596] also points out:
      A)  What should happen if atexit is called during construction of
          a static object? When should this static object be destroyed?
 
      B)  What should happen if a static object is constructed
          during a call to a function that was registered via atexit?
 
            void f() {
              static T t(1);
            }
            void g() {
              static T u(2);
            }
            main() {
              atexit(f);
              atexit(g);
              f();     // ensures 't' is constructed, 'u' is not yet
              exit(0); // now what?  calls 'g' and then 'f'
              // two questions arise:
              // 1) is 'u' destroyed?
              //    (it wasn't constructed prior to the atexit function
              //     calls)
              // 2) has 't' been destroyed prior to calling 'f' again?
           }
 
      C)  What if exit is called from the initialization of an object
          with static storage duration before main even begins to run?
 
    I see two acceptable possibilities:
    1) do as 18.3 says:
      "All functions registered with atexit must have completed before
       any destructor for objects with static storage duration (declared
       either at block scope or at namespace scope) is called." [for the
       order of destruction, see issue 430].
    2) allow what 3.6.3 seems to imply:
      "An implementation shall not destroy objects with static
       storage duration initialized before an atexit call until after
       the function specified in the atexit() call has been called."
 
    Jerry Schwarz wants 2), so that programs like this will work:
      > Let me repeat the reason why I believe the only satisfactory
      > answer for order of destruction and order of atexit is in reverse
      > order of the construction being completed including those of
      > file statics and local statics:
      >
      > static ostream* s;
      > void destroys() {
      >     delete s;
      > }
      >
      > ostream& mystream() {
      >     if ( !s ) {
      >         s = new ostream(....) ;
      >         atexit(delete_s);
      >     }
      >     return *s;
      > }
      >
      > mystream may now be called freely, including from within
      > constructors of static objects and (provided the order of
      > atexit/other static objects is controlled successfully)
      > everything works out fine.
 
  Proposed Resolution:
    Adopt option 1) [Do as 18.3 says].
    All functions registered with atexit must have completed before
    any destructor for objects with static storage duration (declared
    either at block scope or at namespace scope) is called.
    [for the order of destruction, see issue 430].
 
    I believe current implementation limitations will make option 2)
    less viable than option 1).
 
    On Jerry's question A):
      With this proposal, all objects with static storage duration are
      destroyed after the calls to the functions registered with atexit
      have completed.  The timing of the construction of an object with
      static storage duration wrt a call to the atexit function becomes
      irrelevant.
 
    On Jerry's question B)
      Since objects with static storage duration are destroyed after
      all functions registered with atexit have completed, upon exit of
      the program,
      1) the functions registered with atexit are first executed:
        1) g() called first
        2) f() is then called
      2) objects with static storage duration are then destroyed,
         and objects defined in the same translation unit are destroyed
         in reverse order of their initialization:
        1) u is destroyed first
        2) t is then destroyed
 
    On Jerry's question C)
      Objects that have been constructed will be destroyed.  The
      resolution of issue 430 will indicate the order of destruction of
      objects with static storage duration.  Because the standard
      imposes no requirement on what is initialized before main is
      entered, it is unspecified exactly which objects will be destroyed
      if exit is called from the initialization of a an object with
      static storage duration before main even begins to run.
 
    Proposed WP changes:
    3.6.3[basic.start.term] paragraph 1 should say:
      "If atexit is to be called, the implementation shall not destroy
       objects of static storage duration until after the functions
       registered with atexit() have been completed."
 
    6.7[stmt.dcl] paragraph 5 should say:
      "The destruction of local object with static storage duration
       follows the rules described in 3.6.3."