90 Advanced The Hundred
The Initialization Order Fiasco
two .cpp files walk into a linker…
// other.cpp
int answer() { return 42; }
int source = answer();
// main.cpp
#include <iostream>
extern int source; // defined in other.cpp
int duplicated = source * 2;
int main() {
std::cout << "source = " << source << "\n";
std::cout << "duplicated = " << duplicated << "\n";
}
Build it: g++ -std=c++17 main.cpp other.cpp -o demo. What does it print?
Answer
With GCC on x86-64, source prints 42 either way — but duplicated = 0. Swap the file
names (g++ -std=c++17 other.cpp main.cpp) and the same source prints duplicated = 84.
Why¶
Globals within one translation unit are initialized top to bottom; across translation
units, the standard leaves the relative order unspecified. Every global is
zero-initialized before any dynamic initializer runs, so when main.cpp goes first,
duplicated = source * 2 reads source while it is still 0 — the real 42 arrives
moments too late. GCC on Linux happens to run each file's initializers in link order,
which is why reordering the command line flips the answer, and why this bug can hide for
years until an innocent build-system tweak. The decoy: plain int source = 42; would be
constant-initialized at compile time and immune — it takes a runtime call like
answer() to force dynamic initialization. This is unspecified order, not undefined
behavior, and neither -Wall -Wextra nor the linker says a word.
The fix¶
The Meyers singleton: hide the global behind a function-local static, which is initialized on first use (thread-safely, since C++11):
// other.cpp
int& source() {
static int value = answer(); // runs the first time anyone asks
return value;
}
// main.cpp
int& source();
int duplicated = source() * 2; // 84, whatever the link order
Since C++20, constinit on a global demands compile-time initialization, or the build fails.
Takeaway: never let a global's initializer read a global from another translation unit — route the access through a function-local static.
Try it: g++ -std=c++17 main.cpp other.cpp -o demo && ./demo — then swap the file names.
This lesson also needs other.cpp; Compiler Explorer opens main.cpp only.