Introduction
The previous blog post got into details about how to control the order of initializing globals in Visual Studio (in regards to the order in different translation units which is undefined by the C++ standard).
However, the standard doesn’t leave things completely at the compiler’s discretion when it comes to the order within a single translation unit.
This second blog post will describe this and also a not so widely known behavior of when static local objects are terminated.
Initialization order within a single translation unit
While the standard doesn’t define the order to initialize global objects in different translation units, it’s quite specific about the cases when initializing globals within the same translation unit (with the single exception of class template static data members). [1]
The rule for the order is quite simple: Objects are initialized exactly in the order they are defined.
Good practice therefore is to put a list of all definitions of globals at a single place within the cpp file (f.e. at the end). The order these globals are defined then provides a direct overview/documentation of the order these objects will be initialized.
1 2 3 4 5 6 7 8 9 10 11 12 |
[...] namespace Foo { class A { static std::string MyNumber; }; [...] // initialization list std::string g_String; std::string A::MyNumber; } |
In this example, there are two global/static objects defined. Because of the order in the initialization list, g_String will be initialized before A::MyNumber.
Initialization order of static locals
Well known should be the fact that local statics get initialized only once and it’s ensured (by the standard) that the initialization is done prior to the local object being used. Hence, it’s common practice to write constructs like the following one, to ensure that costly operations are performed only on demand:
1 2 3 4 5 |
void foo() { static Bar localBar; localBar.test(); } |
Assume the constructor of the class Bar would allocate some limited resource. Since Bar is defined as a local static, the developer surely expects that the resource is only allocated if foo() is called (and not, if the application doesn’t call foo() at runtime at all). Having dealt with quite a bunch of different build environments, that’s also the behavior the author always experienced in reality.
Regardless, the standard also allows a different behavior and explicitly permits implementations to perform the initialization of static local objects according to how globals are initialized. [2] In effect this means that localBar could get initialized by a certain compiler already when the application starts up.
Termination order of globals and static locals
If you ask 10 C++ developers in which order globals/statics are terminated, you most probably will have 9 out of these 10 tell you that these will be terminated in reverse order of how they were initialized. While this is true in most cases, it doesn’t stand for static locals in all situations.
Try out the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#include <iostream> class B { public: B() { std::cout << "B - ctor" << std::endl; } ~B() { std::cout << "B - dtor" << std::endl; } }; class A { public: static void Test() { std::cout << "A::Test()" << std::endl; static B b; } A() { std::cout << "A - ctor" << std::endl; Test(); } ~A() { std::cout << "A - dtor" << std::endl; } }; int main() { return 0; } A a; |
Running this sample code, you’ll get the following output:
1 2 3 4 5 |
A - ctor A::Test() B - ctor A - dtor B - dtor |
What’s unexpected to most developers here is to see that the local object A is destroyed before B, even though the initialization order was the same (i.e. A was constructed before B).
If you’d slightly modify the example and call Test() inside main() rather than in the ctor for A, you’ll get the “usual” termination order and will see that the dtor of A is called after B’s dtor.
The explanation for this quite specific behavior can be found in the C++ standard as well. [3] In easier words than used in the standard it means that if a static locals is initialized during the construction of a global object, it will be terminated after the object which called the function with the static local in its ctor.
References
[1] C++ 03 standard – 3.6.2 (1)
“[…]Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit.[…]”
[2] C++ 03 standard – 6.7 (4)
“[…]An implementation is permitted to perform early initialization of other local objects with static storage duration […]. Otherwise such an object is initialized the first time control passes through its declaration;[…]”
[3] C++ 03 standard – 3.6.3 (1)
“[…] These objects [objects of static storage duration] are destroyed in the reverse order of the completion of their constructor […]. […] For an object of […] class type, all subobjects of that object are destroyed before any local object with static storage duration initialized during the construction of the subobject is destroyed. […]”