Static initializers in C++ Umesh Nair June 2001 1 Introduction The problem of wrong order of static initialization causes applications to crash. People try several workarounds-changing the order of dynamic libraries, tweaking the linker behavior etc. With the need to support multiple platforms and different environments, this problem needs to be solved in a more robust way. In this document 1, various feasible solutions to avoid static initializer ordering problem are discussed. 2 The Problem Consider the following class declaration: class A { public: void foo() {... ; Consider the following definition of a global object in a translation unit T1: A global_a; Consider the following declaration of class B: extern A global_a; class B { public: 1 Extracted from an internal presentation done in Mentor Graphics Corporation. 1
; B() {... global_a.foo(); An object of B is constructed based on the current state of the global object global_a. This will work fine if all objects of B are allocated on the stack or the heap. The object global_a, like all global objects, is constructed before main(), and will be available when the B object is constructed. But the situation is different if there is a global object of type B defined in a translation unit different than T1. 2 So, if a definition B global_b; appears in a translation unit T2, C++ standard doesn t guarantee that global_a will be initialized before global_b. This will cause the construction of global_b giving undesired effects. 3 Techniques to avoid static initializer ordering problem Several articles have been written on this subject in various newsgroups, books and journals. Many of the suggestions may not be implemented in certain codebase, because of one of the following reasons: 1. The compilers may not support these techniques. 2. Shared library compatibility issue. 3. Issues in a major redesign. 4. Issues in forcing modifications in client. applications The following are some feasible solutions. 3.1 Avoid global variables of non-primitive datatypes If possible, this is the best solution. This may not be possible because of the constraints (2) and (3). However, there are situations, where a global variable may be replaced by a local variable or a class variable. 2 If they are in the same translation unit, C++ standard guarantees that global objects will be initialized in the order they are defined. 2
3.2 Use a lazy allocation technique In lazy allocation techniques, we convert a global object to a non-global object, so that its construction is deferred till the first use. This generally provides an adequate solution, but has the following disadvantages: 1. They are not thread-safe, because there is no global object. 2. They may impact performance, because an extra function call overhead is there, and in many cases, a global object is replaced by a heap object. 3. This will have a problem if there are destruction order dependencies also, because these techniques do not control the order in which the objects are destroyed. Some lazy allocation techniques are considered below: 3.2.1 Replace the global variable with a global pointer that will be dynamically allocated when it is needed Instead of defining the global variable by A global_a; define a function A& get_global_a() { static A* p = new A; return *p; The object is allocated on the heap the first time the function get_global_a() is called. Then onwards, the already allocated object is reused. An advantage of this technique is that, it can be implemented without making much code changes. The only modifications are to replace with A global_a; A& get_global_a() { static A* p = new A; return *p; #define global_a (get_global_a()) 3
and all the declarations in the form (this may include client code as well) with extern A global_a; A& get_global_a(); #define global_a (get_global_a()) If the extern declaration is in a header file only, only that header file needs to be modified, and the client code need not be modified. Advantages: 1. Easy to implement. Needs minimum code changes, with possibly no client code change at all. However, clients should rebuild. Disadvantages: 1. Breaks the shared library compatibility, hence cannot be implemented in a general library within an environment release cycle. 2. The client programs need to be rebuilt, not just reimporting the libraries. 3. The client code also may have to change, if they have declared the global object as extern, rather than #including a header file. 4. Introduces a memory leak by allocating an object and never deleting it. This can lead to the following problems: (a) Certain resources may not be reclaimed or certain cleanups may not be done. When defining an object with this technique, care should be taken that the destructor of the class does not release any resources the operating system cannot release. (b) Output from tools like Purify will be more messy if one memory leak is introduced per global variable. 3.2.2 Modification of the previous technique, with a workaround to avoid memory leaks In the last section, we found a technique whose major drawback is the memory leak. It can be worked around by providing the client a way to delete global pointer. Instead of the technique described in the last section, the declaration 4
extern A global_a; can be replaced with class For_global_a { private: static A* p; For_global_a(); ~For_global_a(); public: static A& get() { if (0 == p) { p = new A; return *p; static void cleanup() { if (0!= p) { delete p; p = 0; ; #define global_a (For_global_a::get()) and the definition A global_a; can be replaced with A* For_global_a::p = 0; so that whoever using this technique can call For_global_a::cleanup(); as the last statement in their main() function. Advantages: 1. All advantages of the previous technique. 2. Can workaround with memory leak. 5
Disadvantages: 1. Not useful when used inside libraries. Need to identify where the cleanup() should be called. 2. The class declaration should be duplicated wherever an extern A global_a is there. If it is only in a header file, this is fine. 3.2.3 Use a local static object (Meyers Singleton) This is essentially the same method discussed above, except that a local static object is used instead of local static pointer holding a dynamically allocated object. Instead of the definition A& get_global_a() { static A* p = new A; return *p; #define global_a (get_global_a()) in section (1), we can use A& get_global_a() { static A a; return a; #define global_a (get_global_a()) 3.3 Force the global objects into the same compilation unit C++ standard guarantees that global/static objects in the same compilation unit are constructed in the same order as they are defined. This fact can be used in order to make sure that a global object is initialized before one of its dependent is initialized. 3.3.1 Use a static member of many static variables In the header file where extern A global_a; 6
is declared, replace it with static class Global_a_wrapper { private: static A* ap; public: A() { if (0 == ap) { ap = new A; ~A() { if (0!= ap) { delete ap; ap = 0; // The following two operators are overloaded to // provide a proxy-interface to the actual object A* operator-> () { return ap; A& operator* () { return *ap;...... global_a; and replace the definition with A global_a; A* Global_a_wrapper::ap = 0; The trick here is, there will be a static object global_a of type Global_a_wrapper in each compilation unit, and all these object share the same real object of type A. Since the header file is #included before the definition of any global object in a translation unit, the static object global_a will be constructed before any of them, thus forcing ap to be allocated before them. Advantages: 7
1. No memory leaks, because the static objects will be deleted before the program terminates. 2. The resulting global_a object preserves all semantics of the original object, so that the client code need not be changed. 3. If there is a problem in the order (For example, two header files having these definitions may be #included in a source file, and a wrong order of definitions happens), it will be caught during compilation, and it can be rectified easily. Disadvantages: 1. If the original global object is declared in the client code as an extern, this mechanism need to be put there, or it should #include the appropriate header file. This forces changing the client code. 2. It breaks the shared library compatibility. 3. Should be careful when we have too many of these objects. The program may fail to compile if header files are #included in the wrong order. So, it is better to include header files having these definitions inside header files rather than source files. This may create a maintenance complexity. 3.3.2 The Nifty Counter approach The technique discussed in the previous section deletes global object ap when the first static object global_a is destroyed. The destruction can be deferred till the last global_a is destroyed. This can be done by using a counter instead of the value of the pointer ap, as follows: static class Global_a_wrapper { private: static A* ap; static size_t counter; public: A() { if (0 == counter++) { ap = new A; ~A() { if (0 == --counter) { delete ap; 8
// The following two operators are overloaded to // provide a proxy-interface to the actual object A* operator-> () { return ap; A& operator* () { return *ap;...... global_a;...... // In the implementation file size_t Flobal_a_wrapper::counter = 0; A* Global_a_wrapper::ap = 0; To implement the Nifty-counter approach, there is another way taking advantage of the placement new operator : { extern A global_a;... static class Global_a_helper { Global_a_helper() { if (0 == counter++) { new (&global_a) A(some_parameter); ~Global_a_helper() { if (0 == --counter) { // global_a.cleanup(); global_a.~a(); global_a_helper; This is messy. Since the construction need to take place twice, the default constructor should not do anything. If anything needs to be initialized, that should be through a non-default constructor called from the constructor of Global_a_helper constructor. Similarly, the destructor of A also will be called twice, so it also should not do anything. (A cleanup function may be called from ~Global_a_helper() to cleanup.). If A belongs elsewhere, this cannot be 9
controlled, and another wrapper class may be needed. This can be implemented in libraries where a global object must be provided for a class it provides. (An example is the std::cout object in the standard iostream library.) 3.4 Solve the problem in the client application, rather than the library There are two techniques to do this. 3.4.1 Avoid accessing a global object from a constructor If no global object is used in a constructor or functions called by a constructor, there won t be any problem. A possible way to handle it to separate the code to access the global object in a separate function and call that in every member function. For example, consider the code: extern A global_a; class B { private: public: B() { // Some stuff global_a.foo(); ; Replace it with: class B { private: bool flag ; public: B() { // Some stuff flag = false; void init() { extern A global_a; global_a.foo(); flag = true; ; 10
and include the following statement in the beginning of every member function of B: if (!flag) { init(); This way, the first member function called will make sure that the initializations needed for B. Advantages: 1. The semantics of the class B remain the same. Doesn t break the shared library compatibility. Disadvantages: 1. This solution requires a fix in the client code (class B) rather than in the library (fix in class A or the global object global_a). Practically, it may be difficult to enforce this. 2. global_a.foo() will not be called until a member function of B is called. This may not be desirable. For example, if this function is called with a parameter specific to the object that calls it, and the order need to be preserved. 3. Care should be taken that init() is not called directly or indirectly from the constructor. So, the member functions called by the constructor should not call init(). Things get complicated if a public member function is called from the constructor. 4. Calling init() in every member function is messy. 3.4.2 Use a lazy-allocation singleton technique to defer the construction of the client object The lazy allocation technique discussed above can be used for a global variable in the client code also. So, global variables whose class has a constructor referring to another global variable (or, for an ad-hoc solution, global variables that cause problems) can be made singletons, thereby making sure that the global object it refer to is constructed before it is constructed. 11
4 Recommended solutions 4.1 Solutions that does not break the shared library compatibility The Shared library compatibility requirement stipulates that no symbols used by any of its client applications should be removed or modified (i.e., the type or semantics is changed) from a shared library, causing an earlier version of an application fail to function, until we declare that our applications will not be compatible with earlier tools and cannot be installed along with those. Generally, such a declaration is done at the beginning of environment releases. Unfortunately, I haven t found any way to solve the static initialization problem in a common library because every solution, except the Nifty counter ( 3.3.2) technique, involves removing a global variable, while the nifty-counter technique is unsuitable for existing libraries. However, solutions can be implemented in the client projects, using a singleton model ( 3.2. Typically, a global variable global_b of class B, which accesses the global variable global_a of class A, can be rewritten as B& get_global_b() { static B b; return b; and the function get_global_b() can be used wherever the global variable global_b was used. Since the variable is constructed at the first invocation of the function, it is guaranteed that the global variable global_a has been constructed by then. However, if this client project is a base library for other projects and requires the shared library compatibility requirement to be satisfied, this fix cannot be implemented there also. Here the only solution is to move the access of the global variable from the constructor to another method. See 3.1 for details. In short, this is done by replacing B() { // Some stuff global a.foo(); with B() { 12
// Some stuff flag = false; void init() { extern A global a; global a.foo(); flag = true; where flag is a member variable of type bool, and calling the init() function from every member function as follows: if (!flag) init(); This can be used as an ad-hoc solution. However, as a permanent solution, the Meyers singleton technique described in the next section should be implemented in the next environment release. Here is a suggestion to implement this: 1. Each group should inspect the code in the executables (and not in the shared libraries), and replace the global variables with Meyers singletons. 2. Each group should identify the global objects in the shared libraries, and implement the Meyers singleton technique to them in a conditionally compiled code segment. This should be tested to make sure it will work, but should be disabled when doing an official build, to preserve shared library compatibility. 3. In the next environment release, the #if macros should be reversed or, preferably, the old code should be completely removed, so that all static initialization problems are solved. It should be properly documented, and all downstream clients, if any, should be notified. Cookbooks should be provided for the downstream clients to migrate. 4.2 Permanent solution that may break shared library compatibility constraint For new code, as well as code in an environment release, Meyer s singleton method appears to be the most appropriate method. It is essentially replacing a global variable global_a of type A with the following function: A& get_global_a() { static A a; 13
return a; There are two problems with this technique. They are: 1. It is not thread safe, and cannot be used in a multi-threaded environment. 2. If we require the destructors to be called in the exact opposite order of the constructors, this technique cannot be used. 5 Further issues - Thread safety, order of destruction Some techniques have been gave some suggestions to overcome these two problems. They are discussed below: 5.1 Making Meyers singleton technique thread-safe John Hershberger suggested the use of a mutex to force other threads to block while one thread is constructing the static object. Taking the pthreads library as an example, it can be implemented as follows: A& get_global_a_1() { static A* p = 0; // For efficiency. We don t want expensive // mutex calls after initialization if (0 == p) { static pthread_mutex_t si_mutex = WHATEVER_MUTEX; pthread_mutex_lock(&si_mutex); // Need to check again, // p might have been changed by another thread if (0 == p) { static A a; p = &a; pthread_mutex_unlock(&si_mutex); return *p; This will assure that this object will be constructed only once by only one thread. 14
5.2 Forcing the destruction order of objects to be the opposite of construction order Don Wakefield suggested a Global Object Manager, which keeps an ordered list of the global objects and its cleanup functions. Global objects can register at the Global Object Manager after they are constructed. The Global Object Manager should be a static object (which should be constructed before any of the global objects), which when destroyed will call the cleanup functions of the global objects in the reverse order. Don and John also suggested that all global objects can be implemented as singleton classes that are inherited from a common base class and having a virtual cleanup() function. Combining these ideas, the following implementation can provide thread-safety as well as right destructor ordering. 1. Define a base class for wrapper singleton classes representing the global objects: class Global_base { public: virtual void cleanup() { delete this; ; Default behavior is to delete the object. However, a child class can provide a different cleanup() function. 2. Implement a Global Object Manager // Function for the for_each algorithm void do_cleanup(global_base* p) { p->cleanup(); class Global_object_manager { typedef vector< Global_base* > vec; private: // Data vec v; private: // Methods // Makes sure that there is only one manager static Global_object_manager& get_manager() { 15
static Global_object_manager gm; return gm; // Updates the list with the object information void update(global_base* p) { v.push_back(p); // Constructor should be private. Only one object (created // through get_manager()) should exist Global_object_manager() : v() { public: // Methods // Destructor should be public // Should destroy the objects in the reverse order ~Global_object_manager() { for_each(v.rbegin(), v.rend(), do_cleanup); ; // This is the only function called by other objects // Registers a global object with the manager static void submit(global_base* p) { get_manager().update(p); This will take care of the destructor-order problem. 3. Now the tricky part is to define a wrapper object for every global object. Here is a possible implementation: class Wrapper_global_a : public Global_base { private: A* ap; static Wrapper_global_a* wap; public: static Wrapper_global_a* allocate() { if (0 == wap) { wap = new Wrapper_global_a; return wap; 16
A* value() { return ap; private: // Constructor to be called by allocate(). Should be private Wrapper_global_a() { ap = new A; ; // Deleting the object should delete ap ~Wrapper_global_a() { delete ap; // In the implementaion file Wrapper_global_a* Wrapper_global_a::wap = 0; This provides a singleton class that provides a wrapper for the global object. allocate() gives the address of a valid static object, which is constructed only once. value() returns the actual object. The cleanup() function will delete the object in the end. 4. Finally, the function giving the object: A& get_global_a_2() { static A* p = 0; if (0 == p) { static pthread_mutex_t si_mutex = WHATEVER_MUTEX; pthread_mutex_lock(&si_mutex); if (0 == p) { Wrapper_global_a* wp = Wrapper_global_a::allocate(); Global_object_manager::submit(wp); p = wp->value(); pthread_mutex_unlock(&si_mutex); return *p; The first time this function is called, a lock is set, the global object is allocated and registered with the Global Object Manager. Then onwards, 17
the allocated object is returned without the overhead of setting a mutex. The Global Object Manager will destroy the wrapper objects in the reverse order, and in that process, will call the cleanup() function of each wrapper object, which in turn will delete the actual object. 18
Contents 1 Introduction 1 2 The Problem 1 3 Techniques to avoid static initializer ordering problem 2 3.1 Avoid global variables of non-primitive datatypes......... 2 3.2 Use a lazy allocation technique................... 3 3.2.1 Replace the global variable with a global pointer that will be dynamically allocated when it is needed........ 3 3.2.2 Modification of the previous technique, with a workaround to avoid memory leaks.................... 4 3.2.3 Use a local static object (Meyers Singleton)........ 6 3.3 Force the global objects into the same compilation unit..... 6 3.3.1 Use a static member of many static variables....... 6 3.3.2 The Nifty Counter approach................. 8 3.4 Solve the problem in the client application, rather than the library 10 3.4.1 Avoid accessing a global object from a constructor.... 10 3.4.2 Use a lazy-allocation singleton technique to defer the construction of the client object................ 11 4 Recommended solutions 12 4.1 Solutions that does not break the shared library compatibility.. 12 4.2 Permanent solution that may break shared library compatibility constraint............................... 13 5 Further issues - Thread safety, order of destruction 14 5.1 Making Meyers singleton technique thread-safe.......... 14 5.2 Forcing the destruction order of objects to be the opposite of construction order.......................... 15 19