MFC Programmer's SourceBook : Thinking in C++
Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

Static initialization dependency

Within a specific translation unit, the order of initialization of static objects is guaranteed to be the order in which the object definitions appear in that translation unit. The order of destruction is guaranteed to be the reverse of the order of initialization.

However, there is no guarantee concerning the order of initialization of static objects across translation units, and there’s no way to specify this order. This can cause significant problems. As an example of an instant disaster (which will halt primitive operating systems, and kill the process on sophisticated ones), if one file contains

// First file
#include <fstream>
ofstream out("out.txt"); 

and another file uses the out object in one of its initializers

// Second file
#include <fstream>
extern ofstream out;
class Oof {
public:
  Oof() { out << "ouch"; }
} oof; 

the program may work, and it may not. If the programming environment builds the program so that the first file is initialized before the second file, then there will be no problem. However, if the second file is initialized before the first, the constructor for oof relies upon the existence of out, which hasn’t been constructed yet and this causes chaos. This is only a problem with static object initializers that depend on each other , because by the time you get into main( ), all constructors for static objects have already been called.

A more subtle example can be found in the ARM. [30] In one file,

extern int y;
int x = y + 1; 

and in a second file,

extern int x;
int y = x + 1; 

For all static objects, the linking-loading mechanism guarantees a static initialization to zero before the dynamic initialization specified by the programmer takes place. In the previous example, zeroing of the storage occupied by the fstream out object has no special meaning, so it is truly undefined until the constructor is called. However, with built-in types, initialization to zero does have meaning, and if the files are initialized in the order they are shown above, y begins as statically initialized to zero, so x becomes one, and y is dynamically initialized to two. However, if the files are initialized in the opposite order, x is statically initialized to zero, y is dynamically initialized to one, and x then becomes two.

Programmers must be aware of this because they can create a program with static initialization dependencies and get it working on one platform, but move it to another compiling environment where it suddenly, mysteriously, doesn’t work.

What to do

There are three approaches to dealing with this problem:

  1. Don’t do it. Avoiding static initializer dependencies is the best solution.
  2. If you must do it, put the critical static object definitions in a single file, so you can portably control their initialization by putting them in the correct order.
  3. If you’re convinced it’s unavoidable to scatter static objects across translation units – as in the case of a library, where you can’t control the programmer who uses it – there is a technique pioneered by Jerry Schwar
z while creating the iostream library (because the definitions for cin, cout, and cerr live in a separate file).

This technique requires an additional class in your library header file. This class is responsible for the dynamic initialization of your library’s static objects. Here is a simple example:

//: C10:Depend.h
// Static initialization technique
#ifndef DEPEND_H
#define DEPEND_H
#include <iostream>
extern int x; // Declarations, not definitions
extern int y;

class Initializer {
  static int init_count;
public:
  Initializer() {
    std::cout << "Initializer()" << std::endl;
    // Initialize first time only
    if(init_count++ == 0) {
      std::cout << "performing initialization"
           << std::endl;
      x = 100;
      y = 200;
    }
  }
  ~Initializer() {
    std::cout << "~Initializer()" << std::endl;
    // Clean up last time only
    if(--init_count == 0) {
      std::cout << "performing cleanup" 
        << std::endl;
      // Any necessary cleanup here
    }
  }
};

// The following creates one object in each
// file where DEPEND.H is included, but that
// object is only visible within that file:
static Initializer init;
#endif // DEPEND_H ///:~ 

The declarations for x and y announce only that these objects exist, but don’t allocate storage for them. However, the definition for the Initializer init allocates storage for that object in every file where the header is included, but because the name is static (controlling visibility this time, not the way storage is allocated because that is at file scope by default), it is only visible within that translation unit, so the linker will not complain about multiple definition errors.

Here is the file containing the definitions for x, y, and init_count:

//: C10:Depdefs.cpp {O}
// Definitions for DEPEND.H
#include "Depend.h"
// Static initialization will force
// all these values to zero:
int x;
int y;
int Initializer::init_count;
///:~

(Of course, a file static instance of init is also placed in this file.) Suppose that two other files are created by the library user:

//: C10:Depend.cpp {O}
// Static initialization
#include "Depend.h"
///:~

and

//: C10:Depend2.cpp
//{L} Depdefs Depend
// Static initialization
#include "Depend.h"
using namespace std;

int main() {
  cout << "inside main()" << endl;
  cout << "leaving main()" << endl;
} ///:~ 

Now it doesn’t matter which translation unit is initialized first. The first time a translation unit containing Depend.h is initialized, init_count will be zero so the initialization will be performed. (This depends heavily on the fact that global objects of built-in types are set to zero before any dynamic initialization takes place.) For all the rest of the translation units, the initialization will be skipped. Cleanup happens in the reverse order, and ~Initializer( ) ensures that it will happen only once.

This example used built-in types as the global static objects. The technique also works with classes, but those objects must then be dynamically initialized by the Initializer class. One way to do this is to create the classes without constructors and destructors, but instead with initialization and cleanup member functions using different names. A more common approach, however, is to have pointers to objects and to create them dynamically on the heap inside Initializer( ). This requires the use of two C++ keywords, new and delete, which will be explored in Chapter 11.


[30]Bjarne Stroustrup and Margaret Ellis, The Annotated C++ Reference Manual , Addison-Wesley, 1990, pp. 20-21.

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru