15

Consider the following program.

struct s { ~s(); };
void f()
{
    static s a;
}

struct t { ~t() { f(); } };
int main()
{
    static s b;
    static t c;
}

I'm trying to figure out what exactly the standard guarantees with respect to the destruction of static objects, but I'm finding the text of C++03[basic.start.term] rather insufficient.

Is the behavior of the program defined? If so, what is the order of destruction of the static objects a, b and c? What would happen if s::~s threw an exception? Please explain your reasoning, preferably with quotes from the standard.

avakar
  • 32,009
  • 9
  • 68
  • 103
  • +1 for the craftiness of this question. It's been a while since I last saw a "real" question :) – Matthieu M. Sep 17 '10 at 18:47
  • I've been wondering about this issue lately, and I agree that the Standards (both 03 and 0x) don't say enough to make things clear. Can anyone provide a link to the comp.lang.c++ thread? A related question: does the C standard say anything about calling `atexit` during execution of a function registered with `atexit`? – aschepler Jul 11 '11 at 00:59
  • @aschleper, here's the link: http://groups.google.com/group/comp.std.c++/browse_thread/thread/4ee9f2bb6209eb0d/c432d6e84a8c8f5d?lnk=gst&q=martin+vejn%C3%A1r#c432d6e84a8c8f5d I'm afraid the thread didn't yield any useful replies. – avakar Jul 11 '11 at 08:20

2 Answers2

8

In the following Objects refers to objects of static storage duration.

Objects in the global namespace are created before main.

Objects in the same compilation unit are created in order of definition.
The order is undefined across different compilation units.

Objects in a namespace are created before any function/variable in that namespace is accessed. This may or may not be before main.

Objects in a function are created on first use.

Objects are destroyed in reverse order of creation. Note the order of creation is defined by the completion of their CONSTRUCTOR (not when it was called). Thus one object 'x' that creates another 'y' in its constructor will result in 'y' being constructed first.

If they were not created then they will not be destroyed.

Is the behavior of the program defined?

So yes the order is well defined

b:   Created
c:   Created
c:   Destroyed Start
a:   Created (during destruction of C)
c:   Destroyed End
a:   Destroyed  (a was created after b -> destroyed before b)
b:   Destroyed

Modify the code to see:

#include <iostream>

struct s
{
    int mx;
    s(int x): mx(x) {std::cout <<  "S(" << mx << ")\n";}
    ~s()            {std::cout << "~S(" << mx << ")\n";}
};
void f()
{
    static s a(3);
}

struct t
{
    int mx;
    t(int x): mx(x) { std::cout << "T(" << mx << ")\n";}
    ~t()
    {                 std::cout << "START ~T(" << mx << ")\n";
                      f();
                      std::cout << "END   ~T(" << mx << ")\n";
    }
};
int main()
{
    static s b(1);
    static t c(2);
}

Output is:

$ ./a.exe
S(1)
T(2)
Start ~T(2)
S(3)
END   ~T(2)
~S(3)
~S(1)
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • Martin, thanks, though `s` has a user-defined *destructor*, not a constructor. – avakar Sep 17 '10 at 14:43
  • No difference. The exception (when thrown from ~S() will cause the T destructr to be called. This will create the a as before. Then the stack unwinds and the objects are destroyed as normal. – Martin York Sep 17 '10 at 14:45
  • Ok, regarding your edit, why is `a` being destroyed after `b`? Can you quote the standard? – avakar Sep 17 '10 at 14:46
  • Sorry Typo. Add some print statements. – Martin York Sep 17 '10 at 14:47
  • Martin, thanks, now it makes sense and it is basically what I would expect. However, the wording of the standard "objects are destroyed in the reverse order of creation" is violated here (construction order is `bca`, the destruction order is `cab`). Additionally, there is the clause 3.6.3/2: "the program has undefined behavior if the flow of control passes through the definition of the previously destroyed local object.", which seems to indicate that the intent is to disallow the construction of static objects during termination. – avakar Sep 17 '10 at 14:56
  • @avakar: When a static is constructed, the `atexit` function is called to invoke the destructor during exit. This functor is registered on a stack, and therefore here the stack gives: b is pushed, c is pushed, c is popped, a is pushed, a is popped, b is popped. That's the pragmatic explanation, don't know if the standard considered the creation of new objects during the exit. – Matthieu M. Sep 17 '10 at 15:04
  • No the reverse order of creation is not broken. 'a' is not built at the point when c is destroyed. So it can not be considered for destruction. 3.6.3/2 does not apply. as the control flow does not pass through a previously destroyed object. That clause is to catch things like double delete were you are destroying a previously destroyed object. Each object here is created and destroyed exactly once. – Martin York Sep 17 '10 at 15:05
  • @Martin: did not you rather meant that the order of creation is determined by the order of completion of their constructors ? – Matthieu M. Sep 17 '10 at 15:06
  • Matthieu, thanks, yes, I'm aware of how compilers do it, I'm trying to figure out what the standard says. – avakar Sep 17 '10 at 15:11
  • @Matthieu M: Probably works something like that, but the standard is deliberately silent on implementation details. – Martin York Sep 17 '10 at 15:12
  • 3.6.3/2 does not apply, but it seems to give an intent. 3.6.3/1 seems to be unsatisfiable (maybe I'm just reading it wrong). – avakar Sep 17 '10 at 15:13
  • 1
    I agree with your final conclusion, and would upvote when you correct some slight errors in it: "Objects in the same compilation unit are created in order of declaration." -> "Objects in the same compilation unit are created in order of definition." and "Objects in a namespace are created before any class/object/method/function in that namespace is accessed." -> "Objects in a namespace are created before any function/variable definition in its translation unit is accessed." (BTW you can remove the "Objects in the global namespace" -> It applies the same to the global namespace). – Johannes Schaub - litb Sep 17 '10 at 15:15
  • @Martin York: I know, the standard is often silent on implementation details (and I am thankful for that). However as avakar noted, the standard doesn't mention static objects built during the destruction. Whether we apply Sutter's definition (an object dies when its destructor begin to run) or we wait for the completion of the destructor, we still get construction `b, c, a` and destruction `c, a, b` and I see nothing in the rules exposed to explain this. Also, do consider Johannes' last remark: instantiation works with translation units, not namespaces. – Matthieu M. Sep 17 '10 at 18:45
5

As has been said, order of destructor calls is the exact reverse of order of completion of constructors (3.6.3/1). In other words (3.8/1), stop of lifetime of an object of static storage duration is the reverse of start of lifetime of an object of static storage duration. So it all comes down to when their constructors are called. Suppose that Printer is a type that outputs something in its constructor in the following examples.

Namespace scope

Objects of namespace scope (global and user defined) are in any case created before the first use of any function or variable that is defined in the same translation unit that the object is defined in (3.6.2/3). This deferred initialization (after main was called) must respect the order of definition with respect to other object definitions in the same translation unit of that objects's definition. (3.6.2/1).

Translation unit 1:

void f() { }

extern Printer a;
Printer b("b");
Printer a("a");
extern Printer c;

Translation unit 2:

Printer c("c");
void f();

If we use f, this will not necessarily force creation of c, because f is not defined in the translation unit where c was defined in. a is created after b because it's defined later.

Block scope

Objects of block scope (local statics) are created when control passes through their definition first or when their block is first entered for PODs (6.7/4). Starting lifetime is tried again the next time control passes throgh it if creation couldn't successfully succeed (in case of an exception) (6.7/4).

void g() { static Print p("P"); }
struct A {
  A() { 
    static int n; 
    if(n++ == 0) throw "X"; 
    cout << "A"; 
    g(); 
  } 
};
void f() {
  try { static A a; } catch(char const *x) { cout << x; }
}

This snippet outputs "XAP".

Class scope

For static data members, the same rule applies about order of initialization according to their definition order within the same translation unit (3.6.2/1). This is because the rule for that is formulated as "objects defined in namespace scope..." rather than "objects of namespace scope...". In C++03 deferred initialization (delaying the construction until a variable/function is used from its translation unit) was only allowed for objects of namespace scope, which was not intended. C++0x allows this also for static data members ("non-local variable with static storage duration").


Thus by taking the above rules and taking into account that destruction order is actually determined by completion of constructors rather than by the start of them, we will get to the order ~c ~a ~b.

If s::~s throws an exception, C++0x says that terminate() is called, and you end up having c destroyed and having ended lifetime of a without completing its destructor if it threw the exception. I can't find anything in the C++03 Standard specifying this. It seems to only specifies that for non-local statics and not also for block-scope statics like C++0x does.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • @avakar I can't find an explicit note that one is allowed to create objects with static storage durations during static destruction phase. But since this is not explicitly forbidden and you are generally allowed to create them during normal execution, you are also allowed to do so during static destruction phase. And it seems that in practice this works well. I don't think that the forbidding of recreation of objects hints at forbidding general creation during this phase. Recreation seems to have no real use and if it would be allowed, I think you could create some funky mutual recursion. – Johannes Schaub - litb Sep 17 '10 at 18:53
  • @litb, I understand that an object cannot be destroyed before it is created... Ok, I have browsed through FCD and it seems that the draft is a little clearer than C++03: [n3092:3.6.3/1] "If the completion of the constructor or dynamic initialization of an object with static storage duration is sequenced before that of another, the completion of the destructor of the second is sequenced before the initiation of the destructor of the first." There you have it, ordering the destruction `~c ~a ~b` violates that paragraph. Am I interpreting the text wrong? – avakar Sep 17 '10 at 19:08
  • @avakar I have not yet delved into the new sequence relations because of all that thread stuff which are way too convoluted for me. But in this case, the paragraph you quoted starts with "Destructors (12.4) for initialized objects (that is, objects whose lifetime (3.8) has begun) with static storage duration are called as a result of returning from main and as a result of calling std::exit (18.5).". So I don't believe that the quoted text includes comparison of `a` and `c` because `a` has not yet started lifetime. But if read isolated, I do agree that this sounds suspicious. – Johannes Schaub - litb Sep 17 '10 at 19:54
  • So I'm starting to agree with you that this is weird. Maybe this should be cleared up in the draft? I recommend you to send an issue report or ask in comp.lang.c++ or such. – Johannes Schaub - litb Sep 17 '10 at 20:03
  • Hmm, you're right, I completely missed out on the importance of the first sentence. I'm now beginning to think that this is not UB, but in fact the destructor of `a` is not called at all. – avakar Sep 17 '10 at 20:14
  • @avakar some interesting insight into the "recreate-is-UB" rule: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#640 – Johannes Schaub - litb Sep 17 '10 at 23:35
  • @litb, just to let you know, I've finally gotten around to sending the question to c.s.c++. – avakar Sep 20 '10 at 13:30
  • @avakar nice. Let's hope there are enough people active to participate despite c.s.c++'s high latency. – Johannes Schaub - litb Sep 20 '10 at 16:56