C++ global variable initialization order

corazza picture corazza · Mar 1, 2014 · Viewed 18.3k times · Source

I don't understand what the following code example does and how it does it:

#include <stdio.h>

int f();

int a = f(); // a exists just to call f

int x = 22;

int f() {
    ++x;
    return 123; // unimportant arbitrary number
}

int main() {
    printf("%d\n", x);
}

When this is ran it prints 23, which is the intuitive answer.

However in C++, global variables are supposed to be initialized in order of definition. That would mean that a should be initialized before x, because it is defined before x. If that was the case, then the function f would have to be called before x was initialized, because the call to f is a part of a's definition.

If f is indeed called before x is initialized, that would mean that f would try to increment x -- the result of which I'm not really certain of (most likely UB, or some gibberish value). Then, after a is initialized, x would be initialized to 22 and the program would print out 22.

Evidently that's not what happens. But what does? What does that code actually do?

It definitely seems like x is set to 22 before a = f() is evaluated, but that would mean that the order of initialization is reversed (I could also be wrong about what initialization is, or when it happens).

Answer

Kerrek SB picture Kerrek SB · Mar 1, 2014

The issue is a little bit subtle; please refer to C++11 3.6.2 for details.

What matters for us is that there are two phases of initialization of "non-local variables with static storage duration" (or "global variables" in colloquial parlance): the static initialization phase and the dynamic initialization phase. The static phase comes first. It looks like this:

int a = 0;
int x = 22;

The dynamic initialization runs afterwards:

a = f();

The point is that static initialization doesn't "run" at all - it only consists of setting values that are known at compile time, so those values are already set before any execution happens. What makes the initialization int x = 22; static is that the initializer is a constant expression.


There are cases where dynamic initialization may be hoisted to the static phase (but does not have to), but this is not one of those cases, because it does not meet the requirement that

the dynamic version of the initialization does not change the value of any other object of namespace scope prior to its initialization

When this hoisting happens, it is permissible that the resulting initial values can be different from if it didn't happen. There's an example in the standard for one such "indeterminate" initialization.