영 초기화
영 초기화 (Zero initialization)
변수를 선언만 하고 초기값을 안 넣었을 때, 전역 변수는 왜 자동으로 0이 될까요? 그 규칙이 바로 **영 초기화(zero initialization)**예요. 객체의 초기 값을 0으로 설정해 주는 초기화 방식인데, 언어에 전용 문법은 없고 다른 초기화들이 내부적으로 수행하는 역할이에요. 이 페이지에서 영 초기화가 정확히 언제 일어나고, 타입별로 어떤 값을 넣는지 정리해 볼게요.
출처: cppreference
본문
문법
주의할 점: 아래는 영 초기화 자체의 문법이 아니에요. 영 초기화는 언어에 전용 문법이 없어요. 아래는 실제로는 다른 종류의 초기화들이지만, 그 초기화가 (가능하면) 영 초기화를 수행하는 예시 문법들이에요.
static T object; (1)
T(); (2)
T t = {}; (2)
T {}; (2) (C++11부터)
CharT array[n] = "short-sequence"; (3)
설명
영 초기화는 다음 상황에서 수행돼요.
- 상수 초기화(constant initialization) 대상이 아닌, 정적(static) 또는 스레드 지역(thread-local)(C++11부터) 저장 기간을 가진 모든 이름 있는 변수에 대해, 다른 어떤 초기화보다 먼저 수행돼요.
- 비클래스 타입과, 생성자가 없는 값 초기화된 클래스 타입 멤버에 대한 값 초기화(value-initialization) 시퀀스의 일부로 수행돼요. 초기화자가 제공되지 않는 집합체(aggregate) 요소의 값 초기화도 포함돼요.
- 어떤 문자 타입의 배열을 너무 짧은 문자열 리터럴로 초기화할 때, 배열의 나머지 부분이 영 초기화돼요.
영 초기화의 효과는 다음과 같아요.
T가std::meta::info라면, 객체는 널 반사 값(null reflection value)으로 초기화돼요. (C++26부터)T가 스칼라 타입(scalar type)이라면, 객체는 정수 리터럴0(영)을T로 명시적으로 변환해 얻은 값으로 초기화돼요.T가 비공용체(non-union) 클래스 타입이라면:- 모든 패딩 비트(padding bit)가 0비트로 초기화돼요.
- 각 비정적 데이터 멤버가 영 초기화돼요.
- 각 비가상 기반 클래스 하위 객체가 영 초기화돼요.
- 객체가 기반 클래스 하위 객체가 아니라면, 각 가상 기반 클래스 하위 객체도 영 초기화돼요.
T가 공용체(union) 타입이라면:- 모든 패딩 비트가 0비트로 초기화돼요.
- 객체의 첫 번째 비정적 이름 있는 데이터 멤버가 영 초기화돼요.
T가 배열 타입이라면, 각 요소가 영 초기화돼요.T가 참조 타입이라면, 아무것도 하지 않아요.
참고 (Notes)
비지역 초기화(non-local initialization)에서 설명했듯이, 상수 초기화되지 않는 정적·스레드 지역(C++11부터) 변수는 다른 어떤 초기화가 일어나기 전에 영 초기화돼요.
비클래스 비지역 변수의 정의에 초기화자가 없다면, 기본 초기화(default initialization)는 아무것도 하지 않아요. 그래서 더 이른 영 초기화의 결과가 그대로 남아요.
영 초기화된 포인터는 그 타입의 널 포인터 값이에요. 널 포인터의 값이 정수 0이 아니어도 마찬가지로 그 값이 돼요.
예시 (Example)
#include <iostream>
#include <string>
struct A
{
int a, b, c;
};
double f[3]; // 세 개의 0.0으로 영 초기화
int* p; // 널 포인터 값으로 영 초기화
// (값이 정수 0이 아니어도 마찬가지)
std::string s; // 불확정 값으로 영 초기화된 다음,
// std::string 기본 생성자에 의해 ""으로 기본 초기화
int main(int argc, char*[])
{
delete p; // 널 포인터 삭제는 안전
static int n = argc; // 0으로 영 초기화된 다음 argc로 복사 초기화
std::cout << "n = " << n << '\n';
A a = A(); // A a{}; 또는 A a = {}; 와 같은 효과
std::cout << "a = {" << a.a << ' ' << a.b << ' ' << a.c << "}\n";
}
출력:
n = 1
a = {0 0 0}
결함 보고 (Defect reports)
다음 동작 변경 결함 보고는 이전에 발표된 C++ 표준에 소급 적용됐어요.
| DR | 적용 대상 | 발표된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 277 | C++98 | 포인터가 값 0의 비상수 표현식(널 포인터 상수가 아님)으로 초기화될 수 있었음 | 값 0의 정수 상수 표현식으로 초기화해야 함 |
| CWG 694 | C++98 | 클래스 타입의 영 초기화가 패딩을 무시했음 | 패딩이 0비트로 초기화됨 |
| CWG 903 | C++98 | 스칼라 타입의 영 초기화가 값 0의 정수 상수 표현식에서 변환된 값으로 초기값을 설정했음 | 객체는 정수 리터럴 0에서 변환된 값으로 초기화됨 |
| CWG 2026 | C++98 | 영 초기화가 상수 초기화보다도 항상 먼저 일어나도록 규정됐음 | 상수 초기화가 적용되면 영 초기화하지 않음 |
| CWG 2196 | C++98 | 클래스 타입의 영 초기화가 기반 클래스 하위 객체를 무시했음 | 그것들도 영 초기화됨 |
| CWG 2253 | C++98 | 영 초기화가 이름 없는 비트 필드에 적용되는지 불명확했음 | 적용됨(모든 패딩 비트가 0비트로 초기화됨) |
더 알아보기
- 초기화 (initialization) 전반에서 이 규칙이 어디에 끼는지 보면 흐름이 잡혀요.
- 기본 초기화 (default initialization)와 값 초기화 (value initialization)는 영 초기화가 내부에서 함께 일어나는 대표적인 경우예요.
- 상수 초기화 (constant initialization)는 영 초기화보다 먼저 일어날 수 있는 초기화예요.
- 복사 초기화 (copy initialization), 직접 초기화 (direct initialization), 리스트 초기화 (list initialization), 집합체 초기화 (aggregate initialization)와 함께 묶어 보세요.
- 기본 생성자 (default constructor), 생성자 (constructor)와 이어서 보면 클래스 초기화 흐름이 완성돼요.