C++ Initialization Order: Static Fiasco, Members, and TU Rules
이 글의 핵심
Complete guide to initialization order in C++: translation units, member declaration order, static fiasco, and production-safe patterns.
What is initialization order—and why it matters
Problem: Global y might be initialized using global x from another translation unit—cross-TU order is unspecified.
file1.cpp:
int compute() { return 100; }
int x = compute(); // dynamic initialization
file2.cpp:
extern int x;
int y = x * 2; // x might not be initialized yet!
This is the static initialization order fiasco.
flowchart TD
subgraph file1["file1.cpp"]
x["int x = compute()"]
end
subgraph file2["file2.cpp"]
y["int y = x * 2"]
end
subgraph order["initialization order"]
q["which runs first?"]
a["unspecified across TUs"]
end
x --> q
y --> q
q --> a
Table of contents
- Initialization phases
- Order within one TU
- Order across TUs
- Member initialization order
- Static initialization order fiasco
- Common mistakes
- Production patterns
- Full example
1. Initialization phases
Three stages
static int a; // zero initialization
constexpr int b = 10; // constant initialization
constinit int c = 20; // C++20: compile-time check
int func() { return 30; }
int d = func(); // dynamic initialization
// Conceptually: zero -> constant -> dynamic
| Phase | When | Example |
|---|---|---|
| Zero | Before dynamic init | static int x; → 0 |
| Constant | Compile time | constexpr int x = 10; |
| Dynamic | Runtime | int x = f(); |
2. Order within one translation unit
Top-to-bottom declaration order for namespace-scope objects in that TU.
int a = 10;
int b = a * 2; // a first -> b = 20
int c = b + a; // b,a first -> c = 30
3. Order across translation units
Unspecified for dynamic initialization of namespace-scope objects in different TUs.
file1.cpp — global Logger
file2.cpp — global Database using Logger in its constructor
If Database initializes before Logger, you may use Logger before its constructor runs—UB.
4. Member initialization order
Declaration order wins, not the order in the ctor initializer list.
struct Data {
int b;
int a;
Data() : a(10), b(a * 2) {
// actual order: b, then a (declaration order)
// b = a*2 runs while a is still uninitialized
}
};
Fix: declare a before b if b depends on a.
Base → members → ctor body
struct Base {
Base() { std::cout << "1. Base\n"; }
};
struct Member {
Member() { std::cout << "2. Member\n"; }
};
struct Derived : Base {
Member m;
Derived() {
std::cout << "3. Derived\n";
}
};
// Output: 1 Base, 2 Member, 3 Derived
5. Static initialization order fiasco
Problem
// file1
int x = 100;
// file2
extern int x;
int y = x * 2; // x might still be 0
Fix 1: Meyers singleton (function-local static)
class Logger {
public:
static Logger& instance() {
static Logger logger; // initialized on first use (C++11 thread-safe)
return logger;
}
void log(const char* msg) { /*...*/ }
private:
Logger() {}
};
class Database {
public:
Database() {
Logger::instance().log("Database created");
}
};
Fix 2: constinit (C++20)
constinit int x = 100;
extern constinit int x;
constinit int y = x * 2; // both constant-init compatible
Fix 3: Lazy init via function
int& get_x() {
static int x = 100;
return x;
}
int& get_y() {
static int y = get_x() * 2;
return y;
}
6. Common mistakes
Member order mismatch
Match declaration order and initializer list.
Cross-TU globals
Avoid non-constant dependencies; prefer function access or constinit where applicable.
Static destruction order
Destructors of globals run in reverse order of initialization—avoid touching another global’s state in a destructor unless order is guaranteed (usually you cannot).
7. Production patterns
Meyers singleton
class ResourceManager {
public:
static ResourceManager& instance() {
static ResourceManager mgr;
return mgr;
}
void load() {}
private:
ResourceManager() {}
ResourceManager(const ResourceManager&) = delete;
ResourceManager& operator=(const ResourceManager&) = delete;
};
constinit constants
constinit int MAX_CONNECTIONS = 1000;
Nifty counter (legacy I/O streams style)
Sometimes used for libraries that must initialize exactly once across TUs—prefer simpler patterns in new code.
8. Example: safer globals
See the Korean article’s ResourceManager / Database / Cache sample—pattern: first use initializes the manager; globals register during dynamic init but only touch the singleton through instance().
Rules summary
| Scope | Order |
|---|---|
| Within one TU | Declaration order |
| Across TUs | Unspecified for dynamic init |
| Members | Declaration order (not ctor list order) |
| Base / members / body | Base → members → constructor body |
| Destruction | Reverse of initialization |
FAQ
Q1: Cross-TU init order?
A: Unspecified for dynamic initialization—do not depend on it.
Q2: What is the static initialization order fiasco?
A: Globals in different TUs can observe each other half-initialized if you depend on order.
Q3: Fixes?
A: Function-local statics, constinit, lazy initialization.
Q4: Member order?
A: Declaration order in the class—must match dependencies.
Q5: What is constinit?
A: C++20—variable must have static initialization; cannot be initialized by a non-constexpr runtime call.
Q6: Resources?
A: cppreference — initialization, Effective C++ Item 4, C++ Primer.
Related posts (internal links)
- Static members
Practical tips
Debugging
- Warnings first
Performance
- Profile
Code review
- Conventions
Practical checklist
Before coding
- Right approach?
- Maintainable?
- Performance?
While coding
- Warnings?
- Edge cases?
- Errors?
At review
- Intent?
- Tests?
- Docs?
Keywords
C++, initialization order, static initialization, fiasco, constinit
Related posts
- Dynamic initialization
- Static init order error