개발/C,C++

Undefined behavior

-=HaeJuK=- 2025. 10. 1. 15:45
728x90
반응형
C++ 댕글링 레퍼런스와 임시 객체의 생명주기

C++에서 반드시 피해야 할 함정: 댕글링 레퍼런스

C++ 프로그래머라면 누구나 한 번쯤 겪게 되는 골치 아픈 문제, 바로 댕글링 레퍼런스(Dangling Reference)입니다. 레퍼런스를 반환하는 함수를 잘못 작성했을 때 발생하는 이 현상은 프로그램 크래시의 주범이 되기도 합니다. 왜 이런 일이 벌어지는지, 어떻게 해결해야 하는지 함께 알아봅시다.

1. 문제의 핵심: 댕글링 레퍼런스란? 👻

댕글링 레퍼런스(Dangling Reference)는 '매달려 있는' 또는 '허공을 가리키는' 참조를 뜻합니다. 참조(Reference)는 이미 존재하는 객체의 별명인데, 원본 객체가 사라지고 없는데도 별명만 남아있는 상태를 말합니다. 이런 참조를 사용하면 프로그램이 잘못된 메모리 영역을 건드리게 되어 크래시(Crash)가 발생하거나 예기치 못한 결과가 나옵니다.

2. 예제 코드 속 문제점: 위험한 레퍼런스 반환

`getEmptyVector()` 함수는 반환 타입이 const std::vector<int>&로, 레퍼런스를 반환합니다.

// 🛑 위험한 코드
const std::vector<int>& getEmptyVector() {
    // 함수가 끝나면 이 임시 객체는 사라집니다.
    return std::vector<int>(); 
}

이 코드는 다음과 같은 문제를 일으킵니다.

  • std::vector<int>()는 함수가 호출될 때 스택(stack)에 임시 객체를 생성합니다.
  • 이 임시 객체는 함수가 끝나는 순간 소멸(destroy)됩니다.
  • 하지만 함수는 이미 사라진 임시 객체를 가리키는 레퍼런스를 반환합니다.

이후 이 레퍼런스를 사용하는 곳에서는 이미 빈 공간이 된 메모리에 접근하게 되므로 문제가 발생합니다.

3. 왜 어떤 코드는 작동하고, 어떤 코드는 깨졌나?

이것은 C++ 컴파일러가 임시 객체의 생명주기를 다루는 방식 때문에 나타나는 현상입니다.

A. for-loop가 작동한 이유: 생명주기 연장 🪄

// 😇 운 좋게 작동한 코드
for( const auto& value : getEmptyVector() ) {
    // 루프가 끝날 때까지 임시 객체가 살아있도록 컴파일러가 보장해줍니다.
    // ...
}

C++11 이후의 범위 기반 for-loop(range-based for-loop)는 특별한 규칙이 적용됩니다. for-loop는 getEmptyVector()가 반환한 임시 객체의 레퍼런스를 받으면, 루프가 끝날 때까지 그 임시 객체를 파괴하지 않고 생명주기를 연장시켜 줍니다. 덕분에 for-loop가 정상적으로 실행될 수 있었던 것입니다.

B. if문에서 깨진 이유: 즉시 소멸 💥

// 😈 문제가 발생한 코드
if( false == getEmptyVector().empty() ) { // 여기서 임시 객체는 즉시 소멸됩니다.
    // ...
}
// 이미 소멸된 객체에 접근하려다 문제가 발생합니다.
for( const auto& value : getEmptyVector() ) { 
    // ...
}

if문 안에서는 getEmptyVector()가 반환한 임시 객체가 if문 표현식이 끝나는 즉시 파괴됩니다. for 루프에 도달하기 전에 이미 레퍼런스가 무효화된 것입니다. 두 번째 getEmptyVector() 호출 시, 이미 파괴된 스택 메모리에 접근하려다 스택 손상(Stack Corruption)이 발생하며 프로그램이 멈추게 됩니다.

4. 올바른 해결책: static 변수 사용 🛡️

가장 안전하고 올바른 해결책은 static 변수를 사용하는 것입니다.

// ✅ 올바르고 안전한 코드
const std::vector<int>& getEmptyVector() {
    // 프로그램이 끝날 때까지 메모리에 계속 남아있습니다.
    static const std::vector<int> s_emptyVector;
    return s_emptyVector;
}

static 변수는 프로그램이 시작될 때 단 한 번만 생성되고, 프로그램이 끝날 때까지 메모리에 계속 남아있습니다. 따라서 이 변수에 대한 레퍼런스를 반환해도 댕글링 레퍼런스 문제가 발생하지 않습니다.

5. 핵심 정리

  • 레퍼런스(&) 반환 시는 원본 객체의 생명주기를 반드시 확인해야 합니다.
  • 임시 객체의 레퍼런스를 반환하는 것은 매우 위험합니다.
  • for-loop와 같은 특정 문법은 임시 객체의 생명주기를 연장해주지만, 이것에 의존해서는 안 됩니다.
  • 기본값이나 빈 객체를 반환할 때는 static 변수를 사용해 안전성을 확보하세요.
728x90