Your program can fail before main
A compact C++ failure mode: global initialization crosses translation units, and startup becomes link-order roulette.
- problem
- Dynamic initialization across C++ translation units can make startup depend on unspecified ordering before main begins.
- scope
- The cross-translation-unit initialization-order failure and the construct-on-first-use mitigation.
- environment
- C++ programs with namespace-scope objects and dynamic initialization in more than one translation unit.
Assumptions
- At least one global constructor depends on another translation unit.
Limitations
- This compact reference does not cover every storage-duration, destruction-order, module, or constant-initialization rule.
Table of contents 3 sections
Distrust invisible startup work
Invisible startup work deserves distrust. Objects with static storage duration in different translation units are initialized in an order the program does not control.
Constant initialization is safe when the compiler can complete it before dynamic startup, but a constructor that reads another translation unit crosses into an ordering regime the source file does not reveal. The dependency graph exists at runtime while the type system and build graph remain silent. Link order, archive extraction, build flags, and refactoring can change the observed sequence without changing either constructor.
Name the cross-unit failure mode
When one global depends on another global from a different translation unit, construction can run too early and fail before main has a chance to establish the system.
The symptom is often misleading: an empty registry, a default-initialized mutex wrapper, a logger that drops the first message, or a crash inside allocator and runtime code. The reproduction may disappear under a different linker or test binary because the implementation changed object ordering, not because the bug was fixed. Destruction has the mirror-image risk when one global uses another after its lifetime ended.
Construct on first use
The practical move is simple: put the dependency behind a function-local static. Construct on first use. Make lifetime explicit. Never let the linker decide your boot sequence.
Since C++11, initialization of a function-local static is synchronized. That makes the first-use boundary explicit and testable, but it does not absolve the design from cycle analysis or teardown policy. For infrastructure registries and process-lifetime services, an intentionally leaked immutable instance can be safer than an elaborate destruction graph. For everything else, prefer explicit ownership passed from main so startup and shutdown order appear in ordinary control flow.
- sourced C++ initialization rules
The language reference documents indeterminately sequenced and unspecified initialization cases across translation units.
inspect source ↗
- reference cppreference: initializationopen ↗
Primary public reference for static and dynamic initialization ordering.