Namespaces
Variants

Undefined behavior

From cppreference.net

C 언어 표준은 다음 범주에 속하는 프로그램을 제외하고 C 언어 프로그램의 관찰 가능한 동작 을 정확히 명시합니다:

  • undefined behavior - 프로그램의 동작에 제한이 없음을 의미합니다. 정의되지 않은 동작의 예로는 배열 범위를 벗어난 메모리 접근, 부호 있는 정수 오버플로우, 널 포인터 역참조, 시퀀스 포인트 없이 동일한 스칼라를 두 번 이상 수정 하는 경우, 다른 타입의 포인터를 통해 객체에 접근하는 경우 등이 있습니다. 컴파일러는 정의되지 않은 동작을 진단할 의무가 없으며(비록 많은 단순한 상황에서 진단이 이루어지기는 하지만), 컴파일된 프로그램이 의미 있는 동작을 할 필요도 없습니다.
  • 미지정 동작 - 두 가지 이상의 동작이 허용되며 구현체는 각 동작의 효과를 문서화할 필요가 없습니다. 예를 들어, 평가 순서 , 동일한 문자열 리터럴 이 구별되는지 여부 등이 있습니다. 각 미지정 동작은 유효한 결과 집합 중 하나를 생성하며, 동일한 프로그램에서 반복될 때 다른 결과를 생성할 수 있습니다.
  • implementation-defined behavior - 각 구현체가 선택 방법을 문서화하는 미지정 동작. 예를 들어, 바이트의 비트 수, 또는 부호 있는 정수 오른쪽 시프트가 산술 시프트인지 논리 시프트인지 여부.
  • locale-specific behavior - 현재 선택된 locale 에 따라 달라지는 구현 정의 동작. 예를 들어, islower 가 26개의 라틴 소문자 이외의 문자에 대해 true를 반환하는지 여부.

(참고: 엄격하게 준수하는 프로그램은 명시되지 않았거나 정의되지 않았거나 구현 정의된 동작에 의존하지 않습니다)

컴파일러는 C++ 구문 규칙이나 의미론적 제약을 위반하는 모든 프로그램에 대해 진단 메시지(오류 또는 경고)를 발행해야 합니다. 해당 프로그램의 동작이 미정의 동작 또는 구현 정의 동작으로 명시되어 있거나, 컴파일러가 그러한 프로그램을 허용하는 언어 확장을 제공하는 경우에도 마찬가지입니다. 미정의 동작에 대한 진단은 달리 요구되지 않습니다.

목차

미정의 동작과 최적화

올바른 C++ 프로그램은 정의되지 않은 동작을 포함하지 않기 때문에, 실제로 UB가 있는 프로그램이 최적화를 활성화한 상태로 컴파일될 때 컴파일러가 예상치 못한 결과를 생성할 수 있습니다:

예를 들어,

부호 있는 오버플로

int foo(int x)
{
    return x + 1 > x; // true 또는 부호 있는 오버플로우로 인한 UB
}

다음과 같이 컴파일될 수 있음 ( 데모 )

foo:
        mov     eax, 1
        ret

범위를 벗어난 접근

int table[4] = {0};
int exists_in_table(int v)
{
    // 처음 4번의 반복 중 하나에서 1을 반환하거나, 범위를 벗어난 접근으로 인해 미정의 동작 발생
    for (int i = 0; i <= 4; i++)
        if (table[i] == v)
            return 1;
    return 0;
}

다음과 같이 컴파일될 수 있음 ( demo )

exists_in_table:
        mov     eax, 1
        ret

초기화되지 않은 스칼라

_Bool p; // 초기화되지 않은 지역 변수
if (p) // 초기화되지 않은 스칼라에 대한 UB 접근
    puts("p is true");
if (!p) // 초기화되지 않은 스칼라에 대한 UB 접근
    puts("p is false");

다음과 같은 출력을 생성할 수 있습니다(이전 버전의 gcc에서 관찰됨):

p는 true입니다
p는 false입니다
size_t f(int x)
{
    size_t a;
    if (x) // x가 0이 아니거나 UB인 경우
        a = 42;
    return a;
}

다음과 같이 컴파일될 수 있음 ( 데모 )

f:
        mov     eax, 42
        ret

잘못된 스칼라

int f(void)
{
    _Bool b = 0;
    unsigned char* p = (unsigned char*)&b;
    *p = 10;
    // b에서 읽는 것은 이제 UB입니다
    return b == 0;
}

다음과 같이 컴파일될 수 있음 ( 데모 )

f:
        mov     eax, 11
        ret

널 포인터 역참조

int foo(int* p)
{
    int x = *p;
    if (!p)
        return x; // 위의 코드가 미정의 동작이거나 이 분기는 절대 실행되지 않음
    else
        return 0;
}
int bar()
{
    int* p = NULL;
    return *p;    // 무조건적인 미정의 동작
}

다음과 같이 컴파일될 수 있음 ( 데모 )

foo:
        xor     eax, eax
        ret
bar:
        ret

realloc에 전달된 포인터 접근 realloc

clang을 선택하여 표시된 출력을 관찰하십시오

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    int *p = (int*)malloc(sizeof(int));
    int *q = (int*)realloc(p, sizeof(int));
    *p = 1; // UB access to a pointer that was passed to realloc
    *q = 2;
    if (p == q) // UB access to a pointer that was passed to realloc
        printf("%d%d\n", *p, *q);
}

가능한 출력:

12

부작용 없는 무한 루프

출력을 확인하려면 clang을 선택하세요

#include <stdio.h>
int fermat()
{
    const int MAX = 1000;
    // Endless loop with no side effects is UB
    for (int a = 1, b = 1, c = 1; 1;)
    {
        if (((a * a * a) == ((b * b * b) + (c * c * c))))
            return 1;
        ++a;
        if (a > MAX)
        {
            a = 1;
            ++b;
        }
        if (b > MAX)
        {
            b = 1;
            ++c;
        }
        if (c > MAX)
            c = 1;
    }
    return 0;
}
int main(void)
{
    if (fermat())
        puts("Fermat's Last Theorem has been disproved.");
    else
        puts("Fermat's Last Theorem has not been disproved.");
}

가능한 출력:

Fermat's Last Theorem has been disproved.

참고문헌

  • C23 표준 (ISO/IEC 9899:2024):
  • 3.4 Behavior (p: TBD)
  • 4 Conformance (p: TBD)
  • C17 표준 (ISO/IEC 9899:2018):
  • 3.4 Behavior (p: 3-4)
  • 4 Conformance (p: 8)
  • C11 표준 (ISO/IEC 9899:2011):
  • 3.4 Behavior (p: 3-4)
  • 4/2 Undefined behavior (p: 8)
  • C99 표준 (ISO/IEC 9899:1999):
  • 3.4 Behavior (p: 3-4)
  • 4/2 Undefined behavior (p: 7)
  • C89/C90 표준 (ISO/IEC 9899:1990):
  • 1.6 용어 정의

참고 항목

C++ 문서 에서 Undefined behavior 참조

외부 링크

1. 모든 C 프로그래머가 정의되지 않은 행동에 대해 알아야 할 것들 #1/3
2. 모든 C 프로그래머가 정의되지 않은 행동에 대해 알아야 할 것들 #2/3
3. 모든 C 프로그래머가 정의되지 않은 행동에 대해 알아야 할 것들 #3/3
4. 정의되지 않은 행동은 시간 여행을 초래할 수 있습니다 (다른 것들도 있지만 시간 여행이 가장 기이합니다)
5. C/C++에서의 정수 오버플로 이해하기
6. 정의되지 않은 행동과 페르마의 마지막 정리
7. NULL 포인터와의 즐거운 시간, 1부 (널 포인터 역참조로 인한 UB로 발생한 Linux 2.6.30 로컬 익스플로잇)