All is well

[C++/ETC] 미정의 동작(Undefined Behavior, UB) 본문

C++/ETC

[C++/ETC] 미정의 동작(Undefined Behavior, UB)

D0YUN 2025. 2. 16. 20:33

 

미정의 동작(Undefined Behavior, UB)은 C 및 C++에서 언어 표준이 특정한 동작을 정의하지 않은 경우를 의미합니다. 즉, 같은 코드라도 실행 환경이나 컴파일러에 따라 예측할 수 없는 결과를 초래할 수 있음을 이야기합니다.

 

미정의 동작이 발생하면 프로그램이 비정상 종료되거나, 잘못된 결과를 출력하거나, 심지어는 정상적으로 실행되는 것처럼 보이지만 내부적으로는 오류를 포함할 수도 있습니다.

 


미정의 동작이 왜 위험한가?

  • 재현 불가능: 같은 코드를 실행해도 실행할 때마다 다른 결과가 나올 수 있습니다.
  • 컴파일러 최적화 영향: 컴파일러UB를 가정하지 않고 최적화하므로, 코드가 비정상적으로 동작할 수 있습니다.
  • 디버깅 어려움: UB가 발생해도 프로그램이 즉시 크래시하지 않을 수도 있어 버그를 찾기 어렵습니다.

따라서 UB를 피하는 것이 안정적인 C++ 프로그래밍의 핵심입니다.

 


미정의 동작이 발생하는 주요 원인

1. 댕글링 포인터 사용

int* ptr = new int(42);
delete ptr;
std::cout << *ptr; // UB 발생

// 발생 이유

  • `new`를 통해 할당된 메모리는 `delete`를 호출하면 해제됩니다.
  • 하지만 `delete` 후에도 `ptr`은 여전히 해제된 메모리 주소를 저장하고 있으며, 운영체제가 이를 즉시 재사용할 수도 있습니다.
  • 이 상태에서 `*ptr`을 참조하면 미정의 동작이 발생할 가능성이 높습니다.

// 방지 방법

  • `delete` 후 `ptr = nullptr;`로 설정합니다.

 

2. 배열 범위 초과 접근

int arr[3] = {1, 2, 3};
std::cout << arr[5]; // UB 발생

// 발생 이유

  • C++은 배열 접근 시 범위 검사를 수행하지 않습니다.
  • 배열 크기를 초과한 인덱스에 접근하면 할당되지 않은 메모리의 값을 읽거나, 심한 경우 세그멘테이션 폴트(Segmentation Fault)가 발생할 수 있습니다.

// 방지 방법

  • `std::vector::at()`을 사용하여 범위 초과를 방지합니다.
std::vector<int> vec = {1, 2, 3};
std::cout << vec.at(5); // 예외 발생, 안전함!
 

 

3. 0으로 나누기

int x = 10;
int y = 0;
std::cout << (x / y); // UB 발생

// 발생 이유

  • 정수를 0으로 나누는 것은 C++에서 미정의 동작으로 간주됩니다.
  • 대부분의 CPU에서 정수 나눗셈 연산에서 0으로 나누면 Divide-by-Zero 예외가 발생하며, 프로그램이 강제 종료될 가능성이 있습니다.
  • 반면, 부동소수점 연산(`float`, `double`)에서는 `0.0 / 0.0`은 `NaN`, `x / 0.0`은 `Infinity`로 처리됩니다.

// 방지 방법

  • 나누기 전에 분모가 0인지 체크합니다.
if (y != 0) {
    std::cout << (x / y);
} else {
    std::cout << "0으로 나눌 수 없습니다.";
}

 

 

4. 초기화되지 않은 변수 사용

int x;
std::cout << x; // UB 발생

// 발생 이유

  • 지역 변수(스택 변수)자동으로 초기화되지 않으며, 해당 메모리의 기존 값(쓰레기 값)그대로 가지게 됩니다.
  • 디버그 빌드에서는 초기화되지 않은 변수를 사용할 경우 경고가 발생할 수 있지만, 릴리즈 빌드에서는 확인이 어렵습니다.

// 방지 방법

  • 변수를 항상 초기화 후 사용합니다.
int x = 0;
std::cout << x;

 

5. 잘못된 타입 변환 (Type Punning)

int x = 42;
double* p = (double*)&x; // 잘못된 형변환
std::cout << *p; // UB 발생

// 발생 이유

  • `int` 타입 변수를 `double*`로 변환하여 접근하면, `int`와 `double`의 메모리 레이아웃이 다르기 때문예상하지 못한 값이 출력될 수 있습니다.
  • CPU가 메모리 정렬(Alignment)을 강제하는 플랫폼에서는 버스 오류(Bus Error) 또는 세그멘테이션 폴트가 발생할 수도 있습니다.
  • C++에서는 strict aliasing rule을 따르므로, 다른 타입의 포인터를 통해 값을 읽는 것은 UB를 유발할 가능성이 있습니다.

// 방지 방법

  • reinterpret_cast 대신 정확한 타입 변환을 사용합니다.
int x = 42;
double y = static_cast<double>(x);
std::cout << y; // 올바른 변환

 


미정의 동작을 방지하는 방법

  • 삭제된 포인터는 `nullptr`로 설정합니다.
  • 배열 접근 시 `std::vector::at()`을 사용합니다.
  • 나누기 연산 전 0을 체크합니다.
  • 변수항상 초기화 후 사용합니다.
  • 올바른 타입 변환을 사용합니다 (`static_cast<>`).

 

'C++ > ETC' 카테고리의 다른 글

[C++/ETC] `!!temp`와 `temp != nullptr` 비교  (1) 2025.02.09