6. 객체 생성/소멸 순서와 정적 초기화 (static init order fiasco)
난이도 중내 리뷰 · C++
해설 · C++
해설 — 객체 생성/소멸 순서와 정적 초기화 (static init order fiasco)
난이도: 중상
요약
저장기간(storage duration)에 따라 객체의 생성·소멸 시점이 다르다. 전역(정적 저장기간) 은 main 진입 전에 같은 번역 단위 내 정의 순서대로 생성되고, 함수 지역 static 은 그 함수가 처음 실행될 때 딱 한 번 생성된다. 소멸은 모든 정적 객체가 프로그램 종료 시 생성 완료의 역순으로, 자동(automatic) 객체는 스코프를 벗어날 때 역순으로 소멸한다.
출력 예측
ctor g1
ctor g2
enter main
ctor main-local
ctor f-local
ctor localStatic
dtor f-local
ctor f-local // 두 번째 f(): static 은 다시 만들지 않음
dtor f-local
leave main
dtor main-local
dtor localStatic
dtor g2
dtor g1
핵심 개념
- 정적 저장기간(전역) 초기화: g1, g2 는 프로그램 시작 시 main 보다 먼저, 한 번역 단위 안에서는 정의된 순서대로 생성된다.
- 함수 지역 static:
static Tracer s(...)는 제어가 그 선언을 처음 통과할 때 초기화된다(lazy). 이후 호출에서는 다시 초기화하지 않는다 → f() 를 두 번 불러도ctor localStatic은 한 번. - 자동 저장기간:
f-local,main-local은 블록 진입 시 생성, 블록 종료 시 소멸. f() 가 두 번 불리니f-local도 두 번 생성/소멸. - 소멸 순서: 정적 객체들은 프로그램 종료 시 생성을 완료한 순서의 역순으로 소멸. 여기서 완료 순서는 g1 → g2 → (main 중) localStatic 이므로 소멸은 localStatic → g2 → g1.
단계별 내부 동작
- main 전:
ctor g1,ctor g2. - main 진입:
enter main, 그다음ctor main-local. - 첫
f():ctor f-local→localStatic()최초 호출이라ctor localStatic→ 함수 끝에서dtor f-local. - 둘째
f():ctor f-local→localStatic()는 이미 초기화됨(출력 없음) →dtor f-local. leave main출력 후 main 반환 → 자동 객체dtor main-local.- 프로그램 종료 정적 소멸:
dtor localStatic→dtor g2→dtor g1.
흔한 오해·함정
- static init order fiasco: 서로 다른 번역 단위(.cpp)에 있는 전역 객체들의 초기화 순서는 표준이 정하지 않는다(unspecified). A.cpp 의 전역이 B.cpp 의 전역에 의존하면, B 가 아직 초기화 전일 수 있어 미초기화 객체 사용(UB)이 난다.
- 해결: Construct-On-First-Use(함수 지역 static). 전역을 직접 두지 말고
T& instance() { static T t; return t; }로 감싸면, 최초 사용 시점에 초기화가 보장돼 순서 의존이 사라진다(이 코드의localStatic형태). 이른바 Meyers 싱글톤. - C++11 이후 함수 지역 static 은 초기화가 스레드 안전하다(여러 스레드가 동시에 처음 진입해도 정확히 한 번만, 나머지는 대기). 단 "초기화"가 안전한 것이지, 이후 그 객체에 대한 동시 접근까지 안전한 것은 아니다.
- 소멸 순서는 "정의 역순"이 아니라 "생성 완료 역순" 이다. lazy 한 지역 static 은 런타임에 생성되므로, 전역보다 늦게 생성돼 전역보다 먼저 소멸할 수 있다(본 예시). 종료 시점에 다른 정적 객체를 참조하는 소멸자는 이미 소멸된 객체를 만질 위험이 있다(static destruction order 문제).
면접 포인트
- 저장기간별 생성/소멸 시점(전역=시작 전·정의순, 지역 static=최초 사용 시 1회, 자동=스코프)을 정확히 구분.
- static init order fiasco 와 Construct-On-First-Use 해법, C++11 지역 static 의 스레드 안전 초기화.
- 소멸은 생성 완료의 역순 — 종료 시 다른 싱글톤을 참조하는 소멸자의 위험(순서 보장 안 됨)을 안다.