Static initializers in C++

Similar documents
COMP6771 Advanced C++ Programming

QUIZ on Ch.5. Why is it sometimes not a good idea to place the private part of the interface in a header file?

Short Notes of CS201

The issues. Programming in C++ Common storage modes. Static storage in C++ Session 8 Memory Management

CS201 - Introduction to Programming Glossary By

Overview. Constructors and destructors Virtual functions Single inheritance Multiple inheritance RTTI Templates Exceptions Operator Overloading

CSE 374 Programming Concepts & Tools. Hal Perkins Spring 2010

COMP6771 Advanced C++ Programming

See the CS 2704 notes on C++ Class Basics for more details and examples. Data Structures & OO Development I

Exception Safe Coding

CSE 374 Programming Concepts & Tools. Hal Perkins Fall 2015 Lecture 19 Introduction to C++

Object-Oriented Programming for Scientific Computing

Instantiation of Template class

DYNAMIC MEMORY ALLOCATION ON REAL-TIME LINUX

QUIZ Friends class Y;

THE BIG FOUR FRIEND FUNCTIONS

Exception Namespaces C Interoperability Templates. More C++ David Chisnall. March 17, 2011

CSCI-1200 Data Structures Spring 2017 Lecture 27 Garbage Collection & Smart Pointers

CSCI-1200 Data Structures Fall 2011 Lecture 24 Garbage Collection & Smart Pointers

Homework 4. Any questions?

CSE 333 Midterm Exam Cinco de Mayo, 2017 (May 5) Name UW ID#

QUIZ. How could we disable the automatic creation of copyconstructors

C++ Programming Lecture 4 Software Engineering Group

The basic operations defined on a symbol table include: free to remove all entries and free the storage of a symbol table

COMP6771 Advanced C++ Programming

6 Architecture of C++ programs

Object Oriented Design

Objects Managing a Resource

Slide Set 14. for ENCM 339 Fall Steve Norman, PhD, PEng. Electrical & Computer Engineering Schulich School of Engineering University of Calgary

Multimedia-Programmierung Übung 3

CSCI-1200 Data Structures Fall 2009 Lecture 25 Concurrency & Asynchronous Computing

Object-Oriented Programming for Scientific Computing

CSE 374 Programming Concepts & Tools

CS 160: Interactive Programming

CS102 C++ Exception Handling & Namespaces

Suppose we find the following function in a file: int Abc::xyz(int z) { return 2 * z + 1; }

2 ADT Programming User-defined abstract data types

Design Patterns in C++

JAVA An overview for C++ programmers

Object Oriented Software Design - II

Programming Style and Optimisations - An Overview

Financial computing with C++

CSE 303: Concepts and Tools for Software Development

Ch. 11: References & the Copy-Constructor. - continued -

Operator overloading. Conversions. friend. inline

Memory Organization. The machine code and data associated with it are in the code segment

C++ Addendum: Inheritance of Special Member Functions. Constructors Destructor Construction and Destruction Order Assignment Operator

Intermediate Programming, Spring 2017*

ECE 449 OOP and Computer Simulation Lecture 14 Final Exam Review

CS3157: Advanced Programming. Outline

A Whirlwind Tour of C++

CS11 Introduction to C++ Fall Lecture 7

N2880 Distilled, and a New Issue With Function Statics

Q1: /8 Q2: /30 Q3: /30 Q4: /32. Total: /100

A brief introduction to C++

C++: Const Function Overloading Constructors and Destructors Enumerations Assertions

Programming in C++ Prof. Partha Pratim Das Department of Computer Science and Engineering Indian Institute of Technology, Kharagpur

CSE 333 Lecture smart pointers

MARKING KEY The University of British Columbia MARKING KEY Computer Science 260 Midterm #2 Examination 12:30 noon, Thursday, March 15, 2012

SERIOUS ABOUT SOFTWARE. Qt Core features. Timo Strömmer, May 26,

Starting to Program in C++ (Basics & I/O)

Agenda CS121/IS223. Reminder. Object Declaration, Creation, Assignment. What is Going On? Variables in Java

An Introduction to C++

September 10,

Compiler Construction D7011E

Kakadu and Java. David Taubman, UNSW June 3, 2003

Lab 2: ADT Design & Implementation

Class Destructors constant member functions

CSE 333 Lecture smart pointers

The pre-processor (cpp for C-Pre-Processor). Treats all # s. 2 The compiler itself (cc1) this one reads text without any #include s

Lecture 14: more class, C++ streams

Programming in C++ Prof. Partha Pratim Das Department of Computer Science and Engineering Indian Institute of Technology, Kharagpur

QUIZ. What is wrong with this code that uses default arguments?

Overview. Constructors and destructors Virtual functions Single inheritance Multiple inheritance RTTI Templates Exceptions Operator Overloading

Fast Introduction to Object Oriented Programming and C++

Creating a String Data Type in C

CS121/IS223. Object Reference Variables. Dr Olly Gotel

Lecture 15a Persistent Memory & Shared Pointers

CSC1322 Object-Oriented Programming Concepts

Part III. Advanced C ++

3.Constructors and Destructors. Develop cpp program to implement constructor and destructor.

Documentation. Programming / Documentation Slide 42

Part X. Advanced C ++

Safety SPL/2010 SPL/20 1

Object-Oriented Principles and Practice / C++

Bounding Object Instantiations, Part 2

QUIZ. How could we disable the automatic creation of copyconstructors

INITIALISING POINTER VARIABLES; DYNAMIC VARIABLES; OPERATIONS ON POINTERS

Midterm Review. PIC 10B Spring 2018

4.1 Introduction Programming preliminaries Constructors Destructors An example... 3

the gamedesigninitiative at cornell university Lecture 7 C++ Overview

COMP6771 Advanced C++ Programming

17. Classes. Encapsulation: public / private. Member Functions: Declaration. Member Functions: Call. class rational { int n; int d; // INV: d!

CSE 333 Section 9 - pthreads

CSE 333 Lecture smart pointers

Structs. Contiguously-allocated region of memory Refer to members within structure by names Members may be of different types Example: Memory Layout

CS 241 Honors Memory

CMSC 202 Section 010x Spring Justin Martineau, Tuesday 11:30am

CS11 Intro C++ Spring 2018 Lecture 3

Digging into the GAT API

Transcription:

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