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).
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.