Why Linking Order Crashes Your C++ Service: The Static Initialization Order Fiasco
A C++ service that crashes after a harmless log module addition is traced back to undefined cross‑file global initialization order, revealing the static initialization order fiasco and how linking order and compiler‑generated init arrays can cause runtime failures.
Problem description
An operations engineer reports that a stable database service crashes immediately after deployment, even though the new version only adds a trivial logging module. The crash occurs before main is entered, with the debugger pointing to the Database constructor.
Root cause: static initialization order fiasco
In C++, objects with static storage duration (global variables, static members, and function‑local statics) are constructed before main. When such objects are defined in different translation units, the C++ standard does not define the order of their initialization, so the program may access an uninitialized object.
Illustrative code
class Logger {
public:
void log(const std::string& msg) { /* write log */ }
};
Logger globalLogger; // global logger object extern Logger globalLogger;
class Database {
public:
Database() {
globalLogger.log("Database initializing...");
// ... database init logic
}
};
Database globalDB; // global database objectLinking order effect
Compiling and linking with different object file orders shows the issue:
g++ main.o database.o logger.o -o app # crashes
g++ main.o logger.o database.o -o app # runs correctlyThe program’s behavior changes solely because the linker merges the .init_array sections of each object file in an unspecified order.
Types of static‑storage variables
Global variables – initialized at program start, live for the whole execution.
Static member variables – also initialized at program start and shared by all class instances.
Local static variables – initialized the first time control reaches their definition.
How the linker builds the init array
For each translation unit the compiler generates a function that constructs its static objects, e.g.:
void __GLOBAL__sub_I_logger.cpp() {
new(&globalLogger) Logger();
}
static void register_init() __attribute__((constructor));The linker places pointers to these functions into the executable’s .init_array. The order of the pointers is undefined, which leads to the observed crash.
Solution: Meyers Singleton
Wrap each global object in a function that returns a reference to a function‑local static. The static is guaranteed to be constructed on first use, and C++11 makes this initialization thread‑safe.
// logger.cpp
Logger& getLogger() {
static Logger instance; // constructed on first call
return instance;
}
// database.cpp
Database& getDatabase() {
static Database instance;
return instance;
}
Database::Database() {
getLogger().log("DB Init"); // safe: logger is initialized before use
}Takeaway
Never rely on the initialization order of globals spread across multiple source files. Use function‑local statics, dependency injection, or explicit initialization sequences to avoid the static initialization order fiasco.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
IT Services Circle
Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
