C++ Zero Initialization | "0 초기화" 가이드
이 글의 핵심
0 초기화는 비트를 0에 맞추는 단계로, 정적·전역·thread_local에서 자동으로 이어지기도 합니다. 지역 자동 변수는 기다리지 않고, 값 초기화·기본 초기화와의 관계·초기화 순서를 알면 디버깅이 쉬워집니다.
0 초기화란?
0 초기화(zero initialization) 는 변수를 0(또는 nullptr, false 등)으로 채우는 초기화입니다. 정적·전역 저장 기간 객체는 프로그램 로드 시 자동으로 0 초기화되며, 값 초기화 {}로 지역 변수도 0으로 맞출 수 있습니다. 기본 초기화와 상수 초기화·집합체 초기화와 구분해 두면 좋습니다.
int global; // 0 (전역)
static int s; // 0 (정적)
void func() {
static int x; // 0 (정적 지역)
int y; // 쓰레기 값 (지역)
}
왜 필요한가?:
- 안전성: 정적/전역 변수가 쓰레기 값을 갖지 않도록 보장
- 예측 가능성: 프로그램 시작 시 일관된 상태
- 자동화: 명시적 초기화 없이도 안전한 기본값
0 초기화 규칙:
| 타입 | 0 초기화 결과 |
|---|---|
int, long, short | 0 |
float, double | 0.0 |
bool | false |
| 포인터 | nullptr |
| 배열 | 모든 요소 0 |
| 클래스 | 모든 멤버 0 초기화 |
struct Data {
int a;
double b;
int* ptr;
};
static Data d; // {0, 0.0, nullptr}
적용 대상
// 0 초기화됨
int global;
static int s;
static double d;
static int* ptr;
// 0 초기화 안됨
void func() {
int local; // 쓰레기 값
}
0 초기화 적용 조건:
-
정적 저장 기간(static storage duration):
- 전역 변수
static변수 (전역, 지역, 클래스 멤버)thread_local변수
-
프로그램 로드 시 자동 적용:
- 프로그래머가 명시적으로 초기화하지 않아도 자동으로 0으로 설정
- 다른 초기화(상수 초기화, 동적 초기화) 전에 수행
#include <iostream>
int g1; // 0 초기화
static int g2; // 0 초기화
thread_local int g3; // 0 초기화
void func() {
static int s1; // 0 초기화
int local; // ❌ 0 초기화 안됨
std::cout << "g1: " << g1 << '\n'; // 0
std::cout << "g2: " << g2 << '\n'; // 0
std::cout << "s1: " << s1 << '\n'; // 0
// std::cout << "local: " << local << '\n'; // 정의되지 않은 동작
}
실무 권장:
- 전역/정적 변수: 0 초기화에 의존해도 안전
- 지역 변수: 항상 명시적으로 초기화 (
int x{};)
다른 초기화와의 관계: 0 초기화는 “한 단계”
정적·전역 객체의 초기화는 표준에서 여러 단계로 설명됩니다. 0 초기화는 그중 첫 단계로, 비트를 0으로 맞추는 쪽에 가깝습니다. 이후 상수 초기화·동적 초기화가 덧붙을 수 있습니다.
| 구분 | 예시 | 0 초기화와의 관계 |
|---|---|---|
값 초기화 int x{} | 지역 int를 0으로 | 0 초기화가 포함될 수 있는 복합 규칙(타입에 따라) |
기본 초기화 int x; | 지역 int | 0 초기화 아님 → 쓰레기 |
집합체 S{} | 멤버를 0으로 채우는 부분 | 0 초기화 + 나머지 규칙 |
“지역 변수도 0으로 두고 싶다”면 0 초기화만 기다리면 안 되고, int x{} 같은 값 초기화를 써야 합니다.
static·전역 vs 지역: 같은 int x;가 다른 이유
저장 기간이 다르면 같은 문법이라도 결과가 달라집니다.
int g; // 정적 저장 기간 → 먼저 0 초기화
void f() {
static int s; // 정적 지역 → 0 초기화
int a; // 자동(지역) → 기본 타입은 미정의 값
thread_local int t; // 스레드 지역 정적 → 0 초기화
}
- 전역·정적·
thread_local: 프로그램/스레드 수명에 걸쳐 한 번만 존재하므로, 구현은 보통 BSS 등에 올려 0으로 채우는 방식으로 맞춥니다. - 일반 지역 변수: 스택(또는 레지스터)에 매 호출마다 새로 잡히므로, 비용을 줄이기 위해 자동으로 0을 쓰지 않는 것이 일반적입니다. 필요하면 값 초기화로 명시하세요.
흔한 오해: “전역은 0이니까 지역도 0에 가깝지 않을까?” → 아닙니다. 지역 int는 읽기 전에 반드시 대입하거나 int x{}로 초기화하세요.
클래스 vs 기본 타입
- 기본 타입의 정적/전역: 0 초기화로 0, nullptr, false 등으로 시작합니다.
- 클래스 타입의 정적/전역: 0 초기화 단계에서 멤버가 기본 타입이면 0에 가깝게 맞고, 이어서 생성자·필요 시 동적 초기화가 올 수 있습니다. POD가 아닌 타입은 “전부 비트 0이 항상 유효한 상태”는 아니므로, 실무에서는 동적 초기화에서 제대로 된 생성자를 두는 편이 안전합니다.
struct Count {
int n; // 정적 정의 시 0 초기화로 n은 0부터
};
Count global_count; // 전역 Count — 집합체 규칙과 조합 시 멤버 0
인스턴스 멤버 int n;는 객체가 자동 저장 기간이면 기본 초기화 경로로 가기 쉬워 쓰레기가 될 수 있으므로, int n{} 또는 생성자에서 초기화하세요.
실전 예시
예시 1: 전역 변수
int counter; // 0
int main() {
std::cout << counter << std::endl; // 0
counter++;
std::cout << counter << std::endl; // 1
}
예시 2: 정적 지역 변수
void func() {
static int callCount; // 0 (첫 호출 시)
callCount++;
std::cout << "호출 횟수: " << callCount << std::endl;
}
int main() {
func(); // 1
func(); // 2
func(); // 3
}
예시 3: 클래스 정적 멤버
class Counter {
static int count; // 선언
public:
Counter() {
count++;
}
static int getCount() {
return count;
}
};
// 정의 (0 초기화)
int Counter::count;
int main() {
Counter c1, c2, c3;
std::cout << Counter::getCount() << std::endl; // 3
}
예시 4: 배열
static int arr[5]; // {0, 0, 0, 0, 0}
int main() {
for (int x : arr) {
std::cout << x << " "; // 0 0 0 0 0
}
}
초기화 순서
// 1. 0 초기화 (정적/전역)
int global1; // 0
// 2. 상수 초기화 (constexpr)
constexpr int global2 = 42;
// 3. 동적 초기화 (런타임)
int global3 = compute();
초기화 단계 상세:
정적/전역 변수는 다음 순서로 초기화됩니다:
-
Zero Initialization (0 초기화):
- 프로그램 로드 시 모든 정적/전역 변수를 0으로 설정
- 바이너리의
.bss세그먼트에 배치 (디스크 공간 절약)
-
Constant Initialization (상수 초기화):
- 컴파일 타임에 값이 결정된 변수 초기화
- 바이너리의
.data세그먼트에 값 내장
-
Dynamic Initialization (동적 초기화):
main()전에 런타임 함수 호출로 초기화- 파일 내 순서는 보장, 파일 간 순서는 미정의
#include <iostream>
int compute() {
std::cout << "compute() 호출\n";
return 100;
}
int g1; // 1단계: 0
constexpr int g2 = 50; // 2단계: 50 (바이너리에 내장)
int g3 = compute(); // 3단계: main() 전 호출
int main() {
std::cout << "main 시작\n";
std::cout << "g1: " << g1 << ", g2: " << g2 << ", g3: " << g3 << '\n';
}
// 출력:
// compute() 호출
// main 시작
// g1: 0, g2: 50, g3: 100
실무 팁:
- 0 초기화: 명시적 초기화 없이도 안전
- 상수 초기화: 성능 최적화
- 동적 초기화: 초기화 순서 문제 주의
자주 발생하는 문제
문제 1: 지역 vs 전역
int global; // 0
void func() {
int local; // 쓰레기 값
std::cout << global << std::endl; // 0
// std::cout << local << std::endl; // 정의되지 않은 동작
}
문제 2: 정적 초기화 순서
// file1.cpp
int x = 10;
// file2.cpp
extern int x;
int y = x + 1; // 순서 보장 안됨
문제 3: 클래스 멤버
class Widget {
int value; // 기본 초기화 (쓰레기 값)
public:
Widget() {}
};
// ✅ 멤버 초기화
class Widget {
int value = 0; // 0 초기화
};
문제 4: const 변수
// ❌ const는 초기화 필수
// const int x; // 에러
// ✅ 초기화
const int x = 0;
const int y{};
명시적 0 초기화
// 모두 동일
int x = 0;
int y{};
int z = int();
int w{0};
명시적 0 초기화 방법 비교:
| 방법 | 예시 | 특징 |
|---|---|---|
| 직접 대입 | int x = 0; | 명확, 전통적 |
| 중괄호 | int x{}; | 값 초기화, 좁히기 방지 |
| 함수 스타일 | int x = int(); | 임시 객체 생성 |
| 중괄호 + 값 | int x{0}; | 명시적, 좁히기 방지 |
실무 권장:
// ✅ 지역 변수: 중괄호 사용 (안전)
void func() {
int counter{};
double sum{};
int* ptr{};
}
// ✅ 전역/정적 변수: 명시적 초기화 (가독성)
int g_counter = 0;
static int s_total = 0;
// ✅ 배열: 중괄호
int arr[5]{}; // {0, 0, 0, 0, 0}
// ✅ 구조체: 중괄호
struct Point { int x, y; };
Point p{}; // {0, 0}
좁히기 변환 방지:
// ❌ 좁히기 변환 허용
int x = 3.14; // OK: 3 (소수점 버려짐)
// ✅ 좁히기 변환 방지
int y{3.14}; // 에러: narrowing conversion
// 실무 활용
double getValue();
int result{getValue()}; // 컴파일 에러 (의도하지 않은 변환 방지)
실무 패턴
패턴 1: 카운터
class RequestCounter {
static int count_; // 0 초기화
public:
RequestCounter() { ++count_; }
static int getCount() { return count_; }
};
int RequestCounter::count_; // 정의 (0 초기화)
패턴 2: 플래그
static bool g_initialized; // false (0 초기화)
void initialize() {
if (!g_initialized) {
// 초기화 로직
g_initialized = true;
}
}
패턴 3: 버퍼
class Buffer {
static char data_[1024]; // 모두 '\0' (0 초기화)
public:
static void clear() {
// 이미 0으로 초기화됨
}
};
char Buffer::data_[1024]; // 정의
FAQ
Q1: 0 초기화는 언제 발생하나요?
A: 정적/전역 변수는 프로그램 로드 시 자동으로 0 초기화됩니다. 지역 변수는 0 초기화되지 않습니다.
Q2: 지역 변수는 0 초기화되나요?
A: 아닙니다. 지역 변수는 쓰레기 값을 가집니다. 명시적으로 int x{};로 초기화해야 합니다.
Q3: 클래스 멤버는 0 초기화되나요?
A:
- 정적 멤버: 0 초기화됨
- 인스턴스 멤버: 기본 초기화 (쓰레기 값), 명시적 초기화 권장
class Widget {
static int s; // 0 초기화
int x; // 쓰레기 값
int y = 0; // 명시적 초기화
};
Q4: 성능 영향은?
A: 프로그램 로드 시 한 번만 수행되며, 일반적으로 성능 영향은 미미합니다. 운영체제가 효율적으로 메모리를 0으로 설정합니다.
Q5: 초기화 순서는?
A:
- Zero Initialization: 0으로 설정
- Constant Initialization: 컴파일 타임 상수
- Dynamic Initialization: 런타임 함수 호출
Q6: 배열은 어떻게 0 초기화되나요?
A: 정적/전역 배열은 모든 요소가 0으로 초기화됩니다.
static int arr[1000]; // 모두 0
static double darr[100]; // 모두 0.0
Q7: 0 초기화 학습 리소스는?
A:
- “C++ Primer” by Lippman, Lajoie, Moo
- “Effective C++” by Scott Meyers
- cppreference.com - Zero initialization
관련 글: 값 초기화, 기본 초기화, 상수 초기화, 집합체 초기화.
한 줄 요약: 0 초기화는 정적/전역 변수를 프로그램 로드 시 자동으로 0으로 설정하는 초기화입니다.
관련 글: 값 초기화, 기본 초기화, 상수 초기화, 집합체 초기화.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Value Initialization | “값 초기화” 가이드
- C++ Default Initialization | “기본 초기화” 가이드
- C++ Constant Initialization | “상수 초기화” 가이드
- C++ Aggregate Initialization | “집합체 초기화” 가이드
관련 글
- C++ Dynamic Initialization |
- C++ Initialization Order 완벽 가이드 | 초기화 순서의 모든 것
- C++ Aggregate Initialization |
- C++ Aggregate Initialization 완벽 가이드 | 집합 초기화
- C++ call_once |