C++ Default Initialization | '기본 초기화' 가이드
이 글의 핵심
초기화자 없이 선언할 때 적용되는 기본 초기화(default initialization)입니다. 지역 기본 타입은 쓰레기 값·미정의 상태가 될 수 있어 읽기만 해도 정의되지 않은 동작일 수 있습니다. 전역·정적과의 차이, 클래스 멤버와의 관계, 값·리스트 초기화와의 비교, 실무 함정을 정리합니다.
기본 초기화란?
초기화자 없이 변수를 선언하면 기본 초기화(default initialization) 가 적용됩니다. 지역 변수에서는 쓰레기 값이 될 수 있어, 안전한 0/기본값이 필요하면 값 초기화나 0 초기화를 쓰고, 정의되지 않은 동작을 피하기 위해 초기화 여부를 구분해 두는 것이 좋습니다.
int x; // 기본 초기화 (쓰레기 값)
int y = 10; // 명시적 초기화
기본 타입
func 함수의 구현 예제입니다.
void func() {
int x; // 쓰레기 값
double d; // 쓰레기 값
int* ptr; // 쓰레기 값
// 사용 전 초기화 필요
x = 10;
d = 3.14;
ptr = nullptr;
}
실전 예시
예시 1: 지역 변수
func 함수의 구현 예제입니다.
void func() {
int x; // 쓰레기 값
// ❌ 초기화 전 사용
std::cout << x << std::endl; // 정의되지 않은 동작
// ✅ 초기화 후 사용
x = 10;
std::cout << x << std::endl; // OK
}
예시 2: 클래스
class Widget {
int value; // 기본 초기화
public:
Widget() {} // value는 쓰레기 값
};
// ✅ 멤버 초기화
class Widget {
int value;
public:
Widget() : value(0) {} // 명시적 초기화
};
예시 3: 배열
void func() {
int arr[5]; // 쓰레기 값
// ❌ 초기화 전 사용
for (int x : arr) {
std::cout << x << " "; // 정의되지 않은 동작
}
// ✅ 초기화
for (int& x : arr) {
x = 0;
}
}
예시 4: 전역 vs 지역
main 함수의 구현 예제입니다.
int global; // 0 (전역)
int main() {
int local; // 쓰레기 값 (지역)
std::cout << global << std::endl; // 0
// std::cout << local << std::endl; // 정의되지 않은 동작
}
정의되지 않은 동작
void func() {
int x;
// ❌ 정의되지 않은 동작
if (x > 0) {}
int y = x + 10;
int* ptr = &x;
std::cout << x;
}
자주 발생하는 문제
문제 1: 초기화 누락
// ❌ 초기화 안함
int sum;
for (int i = 0; i < 10; i++) {
sum += i; // 정의되지 않은 동작
}
// ✅ 초기화
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += i;
}
문제 2: 포인터
// ❌ 초기화 안함
int* ptr;
if (ptr) {} // 정의되지 않은 동작
// ✅ nullptr
int* ptr = nullptr;
if (ptr) {} // OK
문제 3: 클래스 멤버
// ❌ 초기화 안함
class Bad {
int value;
public:
Bad() {} // value는 쓰레기 값
int getValue() { return value; } // 위험
};
// ✅ 초기화
class Good {
int value = 0; // 멤버 초기화
public:
Good() = default;
};
문제 4: 조건문
int x;
// ❌ 초기화 전 사용
if (condition) {
x = 10;
}
std::cout << x; // condition이 false면 쓰레기 값
// ✅ 초기화
int x = 0;
if (condition) {
x = 10;
}
초기화 권장사항
// ✅ 항상 초기화
int x = 0;
int y{};
int* ptr = nullptr;
// ✅ 멤버 초기화
class MyClass {
int value = 0;
double data = 0.0;
};
// ✅ 배열 초기화
int arr[5]{};
흔한 실수와 함정 (추가 정리)
memset으로 해결하려는 습관
C 스타일로 구조체를 0으로 두고 싶다고memset(&s, 0, sizeof s)를 쓰면, 비 POD나std::string같은 멤버가 있을 때 정의되지 않은 동작이 될 수 있습니다. C++에서는 값 초기화T{}, 멤버 초기화, 생성자를 사용하는 것이 안전합니다.- 출력으로 “확인”하기
쓰레기 값을std::cout으로 찍어 보면 “그럴듯한 숫자”가 나올 수 있지만, 그건 정의된 동작이라는 뜻이 아닙니다. 경고를 끄지 말고,-Wall/-Wuninitialized등으로 미사용·미초기화를 잡는 편이 낫습니다. - 배열의 일부만 채우기
int a[10] = {1};는 나머지를 0으로 채우지만,int a[10];는 전부 미정의입니다. “일부만 쓸 거야”라도 읽기 전에 인덱스마다 대입했는지 검토하세요.
FAQ
Q1: 기본 초기화는?
A: 초기화자 없음. 쓰레기 값 가능.
Q2: 위험?
A: 정의되지 않은 동작. 항상 초기화 권장.
Q3: 전역 변수는?
A: 0 초기화됨.
Q4: 클래스는?
A: 기본 생성자 호출. 멤버는 기본 초기화.
Q5: 방지 방법?
A:
- 선언 시 초기화
- 멤버 초기화
- 생성자 초기화
Q6: 기본 초기화 학습 리소스는?
A:
- “C++ Primer”
- “Effective C++”
- cppreference.com
관련 글: 값 초기화, 0 초기화, 집합체 초기화, 정의되지 않은 동작.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Value Initialization | “값 초기화” 가이드
- C++ Zero Initialization | “0 초기화” 가이드
- C++ Aggregate Initialization | “집합체 초기화” 가이드
- C++ Undefined Behavior | “미정의 동작” 완벽 가이드
내부 동작과 핵심 메커니즘
이 글의 주제는 「C++ Default Initialization | ‘기본 초기화’ 가이드」입니다. 여기서는 앞선 설명을 구현·런타임 관점에서 한 번 더 압축합니다. 시스템·런타임 경계(스케줄링, I/O, 메모리, 동시성)를 기준으로 생각하면, “입력이 어디서 검증되고, 핵심 연산이 어디서 일어나며, 부작용(I/O·네트워크·디스크)이 어디서 터지는가”가 한눈에 드러납니다.
처리 파이프라인(개념도)
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
알고리즘·프로토콜 관점에서의 체크포인트
- 불변 조건(Invariant): 각 단계가 만족해야 하는 조건(예: 버퍼 경계, 프로토콜 상태, 트랜잭션 격리)을 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 동일 입력에 동일 출력이 보장되는 순수한 층과, 시간·네트워크에 의해 달라질 수 있는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화/역직렬화, 문자 인코딩, syscall 횟수, 락 경합처럼 “한 번의 호출이 아니라 누적되는 비용”을 의심 목록에 넣습니다.
프로덕션 운영 패턴
실서비스에서는 기능 구현과 함께 관측·배포·보안·비용이 동시에 요구됩니다. 아래는 팀에서 자주 쓰는 최소 체크리스트입니다.
| 영역 | 운영 관점에서의 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율/지연 분위수, 주요 의존성 타임아웃이 보이는가 |
| 안전성 | 입력 검증·권한·비밀 관리가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등한 연산에만 적용되는가, 서킷 브레이커·백오프가 있는가 |
| 성능 | 캐시 계층·배치 크기·풀링·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리, 마이그레이션 호환성이 문서화되어 있는가 |
운영 환경에서는 “개발자 PC에서는 재현되지 않던 문제”가 시간·부하·데이터 크기 때문에 드러납니다. 따라서 스테이징의 데이터 양·네트워크 지연을 가능한 한 현실에 가깝게 맞추는 것이 중요합니다.
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스 컨디션, 타임아웃, 외부 의존성 불안정 | 최소 재현 스크립트 작성, 분산 트레이스·로그 상관관계 확인 |
| 성능 저하 | N+1 쿼리, 동기 I/O, 잠금 경합, 과도한 직렬화 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 클로저/이벤트 구독 누수, 대용량 객체의 불필요한 복사 | 상한·TTL·스냅샷 비교(힙 덤프/트레이스) |
| 빌드·배포만 실패 | 환경 변수·권한·플랫폼 차이 | CI 로그와 로컬 diff, 컨테이너/런타임 버전 핀(pin) |
권장 디버깅 순서: (1) 최소 재현 만들기 (2) 최근 변경 범위 좁히기 (3) 의존성·환경 변수 차이 확인 (4) 관측 데이터로 가설 검증 (5) 수정 후 회귀·부하 테스트.
관련 글
- C++ Aggregate Initialization |
- C++ Aggregate Initialization 완벽 가이드 | 집합 초기화
- C++ call_once |
- C++ Copy Initialization |
- C++ Dynamic Initialization |
이 글에서 다루는 키워드 (관련 검색어)
C++, default-initialization, initialization, undefined, behavior 등으로 검색하시면 이 글이 도움이 됩니다.