정의되지 않은 동작

Undefined behavior

컴퓨터 프로그래밍에서 정의되지 않은 행동(UB)은 컴퓨터 코드가 적용되는 언어 사양에서 동작이 예측 불가능하도록 규정된 프로그램을 실행한 결과이다.이는 언어 사양에 따라 결과가 규정되어 있지 않은 미지정 동작이나 플랫폼의 다른 컴포넌트(ABI 또는 번역자 문서 등)의 문서화를 지연시키는 구현 정의 동작과는 다릅니다.

C커뮤니티에서는 정의되지 않은 행동을 comp.std.c 투고 에 유머러스하게 "비명적인 악마"라고 부르기도 합니다.이 투고에서는, 컴파일러가 선택하는 것은 무엇이든 할 수 있고, 심지어 "악마가 [1]코에서 튀어나오게 한다"고 설명하고 있습니다.

개요

일부 프로그래밍 언어에서는 프로그램 실행 중에 정의되지 않은 동작이 발생하지 않을 경우 사용자가 볼 수 있는 동일한 부작용을 보이는 한 프로그램이 소스 코드와 다르게 작동하거나 제어 흐름이 달라질 수 있습니다.정의되지 않은 동작은 프로그램이 충족하지 않아야 하는 조건 목록의 이름입니다.

C의 초기 버전에서 정의되지 않은 동작의 주요 장점은 다양한 머신을 위한 퍼포먼스 컴파일러의 생산이었습니다.특정 구성은 머신 고유의 기능에 매핑할 수 있었고 컴파일러는 언어에서 부과된 부작용에 맞춰 런타임에 추가 코드를 생성할 필요가 없었습니다.ge. 프로그램 소스 코드는 특정 컴파일러 및 지원하는 플랫폼에 대한 사전 지식으로 작성되었습니다.

단, 플랫폼의 점진적인 표준화에 의해 특히 새로운 버전의 C에서는 이점을 얻을 수 없게 되었습니다.여기서 정의되지 않은 동작의 경우는 일반적으로 코드의 명확한 버그를 나타냅니다.예를 들어, 그 경계를 벗어난 배열을 인덱싱합니다.정의상 런타임은 정의되지 않은 동작이 발생하지 않는다고 가정할 수 있습니다.따라서 일부 비활성 조건을 확인할 필요가 없습니다.컴파일러에게 이것은 또한 다양한 프로그램 변환이 유효하게 되거나 그 정확성의 증명이 단순해지는 것을 의미한다; 이것은 프로그램 상태가 결코 그러한 조건을 만족시키지 않는다는 가정에 따라 정확성이 좌우되는 다양한 종류의 최적화를 가능하게 한다.컴파일러는 또한 프로그래머에게 알리지 않고 소스 코드에 있을 수 있는 명시적 체크도 제거할 수 있습니다.예를 들어, 정의되지 않은 동작을 검출하는 것은 정의상 동작의 보장이 되지 않습니다.따라서 휴대용 페일 세이프 옵션을 프로그래밍하기가 어렵거나 불가능할 수 있습니다(일부 구조에서는 휴대용 솔루션이 가능합니다).

현재의 컴파일러 개발에서는 일반적으로 컴파일러의 퍼포먼스를 평가하여 마이크로 최적화를 중심으로 설계된 벤치마크와 비교하고 있습니다.이는 범용 데스크톱 및 노트북 시장에서 주로 사용되는 플랫폼(amd64 등)에서도 마찬가지입니다.따라서 특정 소스 코드 문의 소스 코드를 런타임에 어떤 것에든 매핑할 수 있으므로 정의되지 않은 동작은 컴파일러 성능을 향상시킬 수 있는 충분한 여지를 제공합니다.

C와 C++의 경우 컴파일러는 이러한 경우 컴파일 시 진단을 제공할 수 있지만, 이러한 경우 구현은 디지털 로직에서 상관하지 않는 용어와 유사하게 올바르게 간주됩니다.정의되지 않은 동작을 호출하지 않는 코드를 작성하는 것은 프로그래머의 책임입니다.단, 컴파일러 실장에서는 이 경우 진단을 발행할 수 있습니다.오늘날의 컴파일러에는 이러한 진단을 가능하게 하는 플래그가 있습니다.예를 들어,-fsanitizegcc 4.9[2]clang에서 "정의되지 않은 동작 검사제"(UBSan)를 활성화합니다.다만, 이 플래그는 디폴트가 아니고, 이 플래그를 유효하게 하는 것은, 코드를 작성하는 사람을 선택하는 것입니다.

경우에 따라서는 정의되지 않은 동작에 특정 제한이 있을 수 있습니다.예를 들어, CPU의 명령 집합 사양은 일부 형식의 명령 동작을 정의하지 않은 채로 둘 수 있지만, CPU가 메모리 보호를 지원하는 경우 사양에는 사용자가 액세스할 수 있는 명령이 운영 체제의 보안에 구멍을 일으킬 수 있다는 포괄적인 규칙이 포함될 수 있습니다. 따라서 실제 CPU는 다음과 같습니다.는 이러한 지시에 따라 사용자 등록을 파괴하는 것은 허용되지만 슈퍼바이저 모드로 전환하는 것은 허용되지 않습니다.

소스 코드에서 발견된 특정 구성 요소가 런타임에 사용 가능한 특정 정의된 메커니즘에 매핑되어 있는 경우 런타임 플랫폼은 정의되지 않은 동작에 대한 몇 가지 제한 또는 보증을 제공할 수도 있습니다.예를 들어 인터프리터는 언어 사양에 정의되어 있지 않은 일부 조작에 대해 특정 동작을 문서화하는 반면 같은 언어의 다른 인터프리터나 컴파일러는 문서화할 수 없습니다.컴파일러는 특정 ABI를 위한 실행 가능한 코드를 생성하여 컴파일러 버전에 따라 달라지는 방식으로 시멘틱 갭을 채운다: 그 컴파일러 버전에 대한 문서와 ABI 사양은 정의되지 않은 동작에 대한 제한을 제공할 수 있다.이러한 실장의 상세 내용에 의존하면, 소프트웨어는 휴대할 수 없게 됩니다만, 특정의 런타임 이외에서 소프트웨어를 사용할 수 없는 경우는, 휴대성은 문제가 되지 않는 경우가 있습니다.

정의되지 않은 동작으로 인해 프로그램이 크래시되거나 검출이 어려워지고 데이터가 소리 없이 손실되거나 잘못된 결과가 생성되는 등 프로그램이 정상적으로 동작하는 것처럼 보이는 장애가 발생할 수 있습니다.

혜택들

조작을 정의되지 않은 동작으로 문서화하는 것으로써, 컴파일러는, 이 조작이 준거한 프로그램에서는 행해지지 않는다고 생각할 수 있습니다.이를 통해 컴파일러는 코드에 대한 더 많은 정보를 얻을 수 있으며 이 정보를 통해 더 많은 최적화 기회를 얻을 수 있습니다.

C 언어의 예를 다음에 나타냅니다.

인트 후우(서명되어 있지 않다  x) {      인트 가치 = 2147483600; /* 32비트 int 및8비트 문자 */를 전제로 합니다.      가치 += x;      한다면 (가치 < > 2147483600)         막대기();      돌아가다 가치; } 

가치x부정적일 수 없으며, 부호 있는 정수 오버플로가 C에서 정의되지 않은 동작임을 감안할 때 컴파일러는 다음과 같이 가정할 수 있습니다.value < 2147483600항상 거짓일 것이다.그 때문에,if스테이트먼트(함수 호출 포함)bar의 테스트 표현식 때문에 컴파일러에 의해 무시될 수 있습니다.if부작용도 없고, 그 상태도 결코 만족할 수 없습니다.따라서 코드는 의미상 다음과 같습니다.

인트 후우(서명되어 있지 않다  x) {      인트 가치 = 2147483600;      가치 += x;      돌아가다 가치; } 

컴파일러가 서명된 정수 오버플로가 랩어라운드 동작을 한다고 가정하도록 강요받았다면 위의 변환은 합법적이지 않았을 것입니다.

코드가 더 복잡하고 인라인과 같은 다른 최적화가 이루어지면 이러한 최적화는 인간에 의해 발견되기 어려워진다.예를 들어, 다른 함수는 위의 함수를 호출할 수 있습니다.

무효 실행_실행(서명되어 있지 않다  *ptrx) {     인트 z;     z = 후우(*ptrx);     하는 동안에 (*ptrx > 60) {         실행_one_task(ptrx, z);     } } 

컴파일러는 자유롭게 컴파일러를 최적화하여while- 값 범위 분석을 적용하여 여기에 루프: 검사foo(), 이것은, 에 의해서 지적된 초기값이 인식되고 있는 것을 알고 있습니다.ptrx(더 이상 정의되지 않은 동작을 트리거하기 때문에) 47을 초과할 수 없습니다.foo()) 。따라서 첫 번째 체크는*ptrx > 60적합 프로그램에서는 항상 false가 됩니다.더 나아가 결과부터z현재 사용되지 않고 있습니다.foo()부작용은 없고 컴파일러는 최적화할 수 있습니다.run_tasks()즉시 반환되는 빈 함수입니다.의 소실while- 루프가 특히 놀라운 경우foo()개별적으로 컴파일된 오브젝트 파일에 정의되어 있습니다.

부호 있는 정수 오버플로를 정의하지 않는 것의 또 다른 장점은 소스 코드의 변수 크기보다 큰 프로세서 레지스터에 변수 값을 저장하고 조작할 수 있다는 것입니다.예를 들어, 소스 코드에 지정된 변수의 유형이 네이티브 레지스터 폭보다 좁으면(예를 들어 64비트 머신의 "int"와 같은 일반적인 시나리오), 컴파일러는 코드 정의 동작을 변경하지 않고 코드 생성 의 변수에 대해 부호 있는 64비트 정수를 안전하게 사용할 수 있습니다.프로그램이 32비트 정수 오버플로 동작에 의존한다면 대부분의 머신 명령의 오버플로 동작은 레지스터 [3]폭에 따라 달라지기 때문에 컴파일러는 64비트 머신을 컴파일할 때 추가 로직을 삽입해야 합니다.

또한 정의되지 않은 동작을 통해 컴파일러와 정적 프로그램 [citation needed]분석을 통해 더 많은 컴파일 시간을 확인할 수 있습니다.

리스크

C 및 C++ 규격에는 여러 가지 형태의 정의되지 않은 동작이 있습니다.이러한 동작은 정의되지 않은 런타임 동작을 희생하면서 컴파일러 구현 및 컴파일 시간 체크에 대한 자유를 증가시킵니다.특히 C의 ISO 표준에는 정의되지 않은 [4]동작의 일반적인 원인을 나열하는 부록이 있습니다.게다가 컴파일러는 정의되지 않은 동작에 의존하는 코드를 진단할 필요가 없습니다.따라서 프로그래머, 심지어 경험이 많은 프로그래머들도 실수로 또는 단순히 수백 페이지에 이르는 언어의 규칙을 잘 모르기 때문에 정의되지 않은 행동에 의존하는 것이 일반적입니다.이로 인해 다른 컴파일러 또는 다른 설정을 사용할 때 버그가 발생할 수 있습니다.동적 정의되지 않은 동작 체크(Clang sanitizer 등)를 활성화하여 테스트 또는 퍼지하면 컴파일러 또는 정적 [5]분석기에 의해 진단되지 않은 정의되지 않은 동작을 포착할 수 있습니다.

정의되지 않은 동작으로 인해 소프트웨어의 보안 취약성이 발생할 수 있습니다.예를 들어 주요 브라우저의 버퍼 오버플로 및 기타 보안 취약성은 정의되지 않은 동작으로 인해 발생합니다.2038년 문제는 부호화된 정수 오버플로로 인한 또 다른 예입니다.2008년에 GCC의 개발자가 정의되지 않은 동작에 의존하는 특정 오버플로우 체크를 생략하도록 컴파일러를 변경했을 때 CERT는 새로운 버전의 [6]컴파일러에 대한 경고를 발행했습니다.Linux Weekly News는 동일한 동작이 PathScale C, Microsoft Visual C++ 2005 및 기타 [7]컴파일러에서도 확인되었다고 지적했습니다.이 경고는 이후 다양한 [8]컴파일러에 대해 경고하도록 수정되었습니다.

C 및 C++의 예

C에서 정의되지 않은 동작의 주요 형태는 공간 메모리 안전 위반, 시간 메모리 안전 위반, 정수 오버플로, 엄밀한 에일리어싱 위반, 얼라인먼트 위반, 시퀀스되지 않은 변경, 데이터 레이스 및 I/O를 실행하거나 종료하지 않는 루프 등으로 [9]크게 분류할 수 있습니다.

C에서는 초기화되기 에 자동변수를 사용하면 정의되지 않은 동작이 발생합니다.정수 나눗셈, 부호 있는 정수 오버플로, 정의된 경계를 벗어난 배열 인덱싱(버퍼 오버플로 참조) 또는 늘 포인터 참조도 마찬가지입니다.일반적으로 정의되지 않은 동작의 인스턴스는 추상 실행 머신을 알 수 없는 상태로 두고 프로그램 전체의 동작을 정의하지 못하게 합니다.

문자열 리터럴을 수정하려고 하면 정의되지 않은 [10]동작이 발생합니다.

 *p = "실패"; // 유효한 C, C++98/C++03에서 사용되지 않음, C++11 현재 잘못된 형식 p[0] = 'W'; // 정의되지 않은 동작 

정수를 0으로 나누면 정의되지 않은 [11]동작이 발생합니다.

인트 x = 1; 돌아가다 x / 0; // 정의되지 않은 동작 

특정 포인터 조작으로 인해 정의되지 않은 [12]동작이 발생할 수 있습니다.

인트 arr[4] = {0, 1, 2, 3}; 인트 *p = arr + 5;  // 범위를 벗어난 인덱싱에 대한 정의되지 않은 동작 p = 0; 인트 a = *p;        // null 포인터를 참조하기 위한 정의되지 않은 동작 

C와 C++에서 오브젝트에 대한 포인터의 관계 비교는 포인터가 같은 오브젝트의 멤버 또는 같은 [13]배열의 요소를 가리키는 경우에만 엄격하게 정의됩니다.예:

인트 주된(무효) {   인트 a = 0;   인트 b = 0;   돌아가다 &a < > &b; /* 정의되지 않은 동작 */ } 

값 반환 함수의 끝에 도달하는 것(제외)main()return 문이 없는 경우 함수 호출 값이 [14]발신자에 의해 사용되는 경우 정의되지 않은 동작이 발생합니다.

인트 f() { }  함수 호출 값이 사용되는 경우 /* 정의되지 않은 동작*/ 

시퀀스 포인트 사이의 개체를 여러 번 변경하면 정의되지 않은 [15]동작이 생성됩니다.C++11 [16]현재 시퀀스 포인트와 관련하여 정의되지 않은 동작을 일으키는 원인에는 상당한 변화가 있습니다.최신 컴파일러는 동일한 [17][18]오브젝트에 대해 여러 차례 수정되지 않은 경우 경고를 발생시킬 수 있습니다.다음 예제에서는 C와 C++ 모두에서 정의되지 않은 동작이 발생합니다.

인트 f(인트 i) {   돌아가다 i++ + i++; /* 정의되지 않은 동작: i */에 대한 두 개의 시퀀스되지 않은 수정 } 

두 시퀀스 포인트 간에 객체를 수정할 때 저장할 값을 결정하는 것 이외의 목적으로 객체의 값을 읽는 것도 정의되지 않은 [19]동작입니다.

a[i] = i++; // 정의되지 않은 동작 인쇄물(%d %d\n", ++n, (2, n)); // 정의되지 않은 동작도 있습니다. 

C/C++에서는 음수이거나 이 값의 총 비트수 이상인 비트수만큼 값을 이동하면 정의되지 않은 동작이 발생합니다.(컴파일러 벤더에 관계없이) 가장 안전한 방법은 항상 (컴파일러의 올바른 피연산자) 시프트 비트 수를 유지하는 것입니다.<<그리고.>> 비트 연산자)의 범위는 다음과 같습니다.<0, sizeof(value)*CHAR_BIT - 1> (여기에서)value왼쪽 오퍼랜드).

인트 숫자 = -1; 서명되어 있지 않다 인트  = 1 << > 숫자; //음수로 표시됨 - 정의되지 않은 동작  숫자 = 32; //또는 31보다 큰 숫자  = 1 << > 숫자; //리터럴 '1'이 32비트 정수로 입력됨 - 이 경우 31비트 이상 이동은 정의되지 않은 동작입니다.  숫자 = 64; //또는 63보다 큰 숫자 서명되어 있지 않다   밸브2 = 1ULL << > 숫자; //리터럴 '1ULL'이 64비트 정수로 입력됩니다. 이 경우 63비트 이상 이동하면 정의되지 않은 동작이 됩니다. 

「 」를 참조해 주세요.

레퍼런스

  1. ^ "nasal demons". Jargon File. Retrieved 12 June 2014.
  2. ^ GCC 미정의 동작 세정제– ubsan
  3. ^ "A bit of background on compilers exploiting signed overflow".
  4. ^ ISO/IEC 9899:2011© J.2.
  5. ^ John Regehr. "Undefined behavior in 2017, cppcon 2017". YouTube.
  6. ^ "Vulnerability Note VU#162289 — gcc silently discards some wraparound checks". Vulnerability Notes Database. CERT. 4 April 2008. Archived from the original on 9 April 2008.
  7. ^ Jonathan Corbet (16 April 2008). "GCC and pointer overflows". Linux Weekly News.
  8. ^ "Vulnerability Note VU#162289 — C compilers may silently discard some wraparound checks". Vulnerability Notes Database. CERT. 8 October 2008 [4 April 2008].
  9. ^ Pascal Cuoq and John Regehr (4 July 2017). "Undefined Behavior in 2017, Embedded in Academia Blog".
  10. ^ ISO/IEC(2003)ISO/IEC 14882:2003(E): 프로그래밍 언어 - C++ © 2.13.4 문자열 리터럴 [lex.string]파라.2
  11. ^ ISO/IEC(2003)ISO/IEC 14882:2003(E): 프로그래밍 언어 - C++ 5 5.6 곱셈 연산자 [expr.mul]파라.4
  12. ^ ISO/IEC(2003)ISO/IEC 14882:2003(E): 프로그래밍 언어 - C++ 5 5.7 가법 연산자 [expr.add] 파라.5
  13. ^ ISO/IEC(2003)ISO/IEC 14882:2003(E): 프로그래밍 언어 - C++ © 5.9 관계 연산자 [expr.rel]파라.2
  14. ^ ISO/IEC(2007)ISO/IEC 9899:2007(E): 프로그래밍 언어 - C©6.9 외부 정의 패러.1
  15. ^ ANSI X3.159-1989 프로그래밍 언어 C, 각주 26
  16. ^ "Order of evaluation - cppreference.com". en.cppreference.com. Retrieved 9 August 2016.
  17. ^ "Warning Options (Using the GNU Compiler Collection (GCC))". GCC, the GNU Compiler Collection - GNU Project - Free Software Foundation (FSF). Retrieved 2021-07-09.
  18. ^ "Diagnostic flags in Clang". Clang 13 documentation. Retrieved 2021-07-09.
  19. ^ ISO/IEC(1999)ISO/IEC 9899:1999(E): 프로그래밍 언어 - C6 6.5 Expressions 패러.2

추가 정보

  • 피터데어 린든, C 프로그래밍 전문가입니다ISBN 0-13-177429-8
  • UB Canaries (2015년 4월), John Reger (미국 유타 대학교)
  • 2017년(2017년 7월)에 정의되지 않은 행동 Pascal Cuoq(프랑스 TrustInSoft) 및 John Regehr(미국 유타 대학교)

외부 링크

  • C99 표준의 수정판.#pragma에 대해서는 섹션 6.10.6을 참조하십시오.