Undefined behavior
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
초기화되지 않은 스칼라
다음과 같은 출력을 생성할 수 있습니다(이전 버전의 gcc에서 관찰됨):
p는 true입니다 p는 false입니다
다음과 같이 컴파일될 수 있음 ( 데모 )
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을 선택하여 표시된 출력을 관찰하십시오
가능한 출력:
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 로컬 익스플로잇) |