주간 팁 #182: 정수형 변수를 초기화하세요!
원래 게시일: 2020년 7월 23일
최종 업데이트: 2020년 7월 23일
바로가기: abseil.io/tips/182
“결정의 순간, 할 수 있는 가장 좋은 일은 옳은 일을 하는 것이고, 그다음으로 좋은 것은 틀린 일을 하는 것이며, 가장 나쁜 것은 아무것도 하지 않는 것이다.”
– 시어도어 루즈벨트
소개
C++에서는 변수를 초기화하지 않고 남겨두는 일이 너무 쉽습니다. 이는 거의 모든 초기화되지 않은 객체에 대한 접근이 정의되지 않은 동작(Undefined Behavior)을 초래하기 때문에 매우 위험합니다. C++에서 기본 초기화(default initialization)는 여러 형태 중 기본값으로 제공되지만, 항상 변수를 초기화하는 것은 아닙니다.
Trivial 타입의 기본 초기화
{
bool bool_one;
bool bool_two = bool_one;
}
위 코드 스니펫이 정의되지 않은 동작을 유발한다는 사실을 아는 사람은 많지 않습니다. 첫 번째 줄에서 bool_one
은 기본 초기화되지만, 이는 변수의 값을 반드시 초기화하는 것을 보장하지 않습니다. 결과적으로 bool_one
은 초기화되지 않은 채 남아 있으며, bool_two
는 초기화되지 않은 값을 읽으려고 시도하면서 정의되지 않은 동작을 유발합니다.
C++에서 기본 초기화가 어떻게 작동하는지 이해하려면 이를 명확히 구분해야 합니다.
기본 초기화 동작
- 기본 생성자가 있는 타입: 대부분의
class
타입은 기본 초기화 시 항상 기본 생성자를 호출합니다. 예를 들어,std::string str;
은std::string str{};
처럼 항상 초기화됩니다. - 생성자가 없는 타입:
bool
과 같은 타입은 두 가지 경우로 나뉩니다.- 정적(static) 또는 네임스페이스 스코프에서 정의된 변수는 “값 초기화(value initialization)”가 수행되어 초기화됩니다.
- 하지만 블록 스코프의 변수는 기본 초기화 시 아무 작업도 수행하지 않아 초기화되지 않은 상태로 남습니다.
따라서 위 코드에서 bool_one
은 초기화되지 않은 상태로 남습니다. 이는 bool
이 생성자가 없고, bool_one
이 블록 스코프 내에 정의되었기 때문입니다.
Trivial 타입: 어떤 타입이 초기화되지 않을 수 있나?
C++에서는 C에서 유래한 타입, 즉 “Trivial” 타입(생성자가 없는 타입)을 지원합니다. 여기에는 int
, double
과 같은 기본 타입, 그리고 멤버 초기화 구문이 없는 struct
타입이 포함됩니다. 또한 모든 원시 포인터 타입(MyClass*
등)도 이에 해당합니다.
C++에서 객체를 초기화하지 않도록 허용하는 이유는 성능 또는 초기값이 정말로 필요 없는 경우와 같은 드문 상황에서 유용하기 때문입니다. 하지만 이러한 초기화되지 않은 값의 접근은 대부분 정의되지 않은 동작을 초래합니다.
Trivial 객체 초기화 제안
일반적으로 코드를 작성할 때, 초기화되지 않은 객체를 사용하지 않는 것이 좋습니다. 특별한 이유(성능 최적화 등)가 없는 한 Trivial 객체를 초기화하는 것이 바람직합니다. 다음은 몇 가지 올바른 초기화 예제입니다:
변수 초기화 예제
float ComputeValueWithDefault() {
float value = 0.0; // 기본 값을 제공하여 초기화를 보장합니다.
ComputeValue(&value);
return value;
}
struct
멤버 초기화 예제
struct MySequence {
// 멤버 초기화 구문을 사용하여 초기화를 보장합니다.
MyClass* first_element = nullptr;
int element_count = 0;
};
MySequence GetPopulatedMySequence() {
MySequence my_sequence; // 멤버 초기화로 인해 안전합니다.
MaybePopulateMySequence(&my_sequence);
return my_sequence;
}
Trivial 타입에 대한 별칭 사용 지양
기본 타입(int
, float
등)에 별칭을 만드는 것은 코드의 가독성을 떨어뜨리고 초기화를 놓치기 쉽게 만듭니다.
{
using KeyType = float; // C++ 스타일 별칭
typedef bool ResultT; // C 스타일 별칭
// [여러 줄의 코드...]
// 예상치 못하게 초기화되지 않은 변수!
KeyType some_key;
ResultT some_result;
}