Static initialization order fiasco

2019-01-19 13:47发布

问题:

In his "Thinking in C++" (Chapter 10) Eckel describes a technique that was pioneered by Jerry Schwarz to solve the fiasco. He says that if we want to initialize x to 100 and y to 200 and share them among all translation units, we create an Initializer.h that looks like this:

extern int x;
extern int y;
class Initializer {
   static int initCount;
   // if (initCount++ == 0) x = 100 & y = 200
   /* ... */
};
static Initializer init;

And in implementation file we have

#include "Initializer.h"
int x;
int y;
int Initializer::initCount;

and Eckel says that "static initialization (in implementation file) will force all these values to zero".

Let me consider the following case: the compiler processes the implementation file after some other file with that header included (it means that x and y have been already set to 100 and 200 in that other file). The compiler sees int x, so what will it do? Will it set x and y to zero eliminating initialization and all possible changes in previous files? But if it does, then initCount will also be set to zero, breaking down the whole technique.

回答1:

But if it is true, and the compiler handles the implementation file after some another file, than it will set x and y to zero eliminating initialization and all possible changes in previous files?

I'm not sure what you mean by this. If x and y are defined in other files, then you have a linker clash and the program simply won't compile.

If x, y and most importantly Initializer::initCount are implemented in this way, there will be unique instances of them in the program; they are effectively global and will be initialized to 0 at program start, before any Initializer is constructed (due to inclusion of the header declaring a static instance of that class). Each construction of a static Initializer will first check whether any other Initializers have been constructed due to the if (initCount++ == 0) etc.

The first Initializer ctor to run (still before entering main) will thus set all three values.



回答2:

What is done in "Initializer" is assignment, not initialization (assuming valid syntax).

As such, it "solves" the static initialization order fiasco for your special case, because there is no fiasco in the first place. x and y are integers, they don't call each other at unpredictable times, and on top of that they live in the same translation unit too. The compiler will just initialize them proper. It's fine if you assign values in a defined order afterwards, but it's only more complex, not any better.

For the static initialization order fiasco to appear, you would need a situation like: constructor of x needs the value of y (or the other way around) and they are in different translation units. Therefore, it's a 50:50 chance whether this works or not.

Now, the "Initializer" struct will correctly assign values in a defined order, but at that time, the constructors of x and y have already run, because you can't assign to what has not been constructed... so it would not avoid the problem at all, if it existed.

Construct on first use is the common way of dealing with this problem. There are different flavours (each with its own advantages and disadvantages) of that technique, such as for example:

x& get_x() { static x *xxx = new x(); return *xxx; }


回答3:

Assuming you mean any possible use and initialization at static-init scope in other source files then you're absolutely correct: If the compiler decided to run this file's static initialization after that in other files then you'll undo that other work.

In many cases you can save yourself a tremendous amount of headaches by just not using globals/statics at all.



回答4:

The global x and y will be initialized to zero when the program is loaded, before any code was executed. When any Initializer is created, x and y are already initialized to zero. Things happen in that order:

  1. Program is loaded
  2. Global and static variables are zero initialized (x and y get their 0 values)
  3. Global objects are constructed (the Initializer sets x and y to 100 and 200)


回答5:

Why not declare (at file scope, in a single translation unit):

int x = 100;
int y = 200;

x and y will be stored in the image's read/write section so they are initialised before any code in the process executes. You don't need to worry about initialisation order for plain-old-data.