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."