[2026] C++ Initialization Order: Static Fiasco, Members, and TU Rules
이 글의 핵심
C++ initialization phases: zero, constant, dynamic; per-TU vs cross-TU order; member order vs initializer list; static initialization order fiasco; Meyers singleton and constinit.
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. 아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
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
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// file1
int x = 100;
// file2
extern int x;
int y = x * 2; // x might still be 0
Fix 1: Meyers singleton (function-local static)
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
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
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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)
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?