**정의되지 않은 동작(Undefined Behavior, UB)**란 언어 표준이 프로그램의 동작을 정의하지 않는 상황을 말합니다. 컴파일러는 프로그램에서 원하는 모든 작업을 수행할 수 있습니다: 프로그램이 중단되거나 잘못된 결과를 내거나 심지어 '작동할' 수도 있습니다. UB는 배열의 경계를 넘는 오류, 널 포인터 역참조 등으로 인해 발생합니다.
int arr[5]; arr[10] = 42; // UB: 배열의 경계를 넘어섬 int* p = nullptr; *p = 1; // UB: 0의 역참조
UB를 피하는 방법은 표준을 준수하고, 현대 도구(ASan, UBSan, valgrind)를 사용하며, 원시 포인터는 사용하지 않고 안전한 코드를 작성하는 것입니다.
만약 프로그램의 한 부분에서 UB가 발생하였다면, 전혀 '독립적인' 다른 코드 부분에 영향을 끼칠 수 있나요?
네! 컴파일러는 최적화 과정에서 UB가 발생했음을 발견하면 예기치 않은 변환을 수행할 수 있습니다.
void foo(int* p) { if (p == nullptr) return; *p = 5; // 만약 p가 nullptr가 아니었다면, 이것은 UB입니다! 그러나 컴파일러는 p가 항상 유효하다고 가정하여 검사를 제거할 수 있습니다. }
이야기
한 대기업 서버에서 널 포인터 역참조 때문에 불규칙적인 프로세스 중단이 발생하였고, 이를 재현하기가 어려웠습니다: 디버그에서는 모든 것이 작동했지만, 릴리스에서는 그렇지 않았습니다.
이야기
32비트에서 64비트로 코드를 포팅하는 과정에서 데이터 유형이 혼동되어, int와 포인터 간의 캐스트가 사용되었습니다. 어떤 기계에서는 작동했지만, 다른 기계에서는 크래시와 이상한 아티팩트가 발생했습니다.
이야기
인터넷에는 무해한 UB(배열 경계 초과)가 전체 프로그램의 동작을 방해하는 경우가 보고된 바 있습니다: 컴파일러는 배열 작업을 삭제했을 뿐만 아니라, 오류와 전혀 관련이 없는 코드의 일부도 '최적화'해 버렸습니다.