정의되지 않은 동작이있는 분기를 도달 할 수 없다고 가정하고 데드 코드로 최적화 할 수 있습니까? 제어 흐름이이 명령문에 도달하면 동작이

다음 진술을 고려하십시오.

*((char*)NULL) = 0; //undefined behavior

정의되지 않은 동작을 명확하게 호출합니다. 주어진 프로그램에 그러한 명령문이 존재한다는 것은 전체 프로그램이 정의되지 않았거나 제어 흐름이이 명령문에 도달하면 동작이 정의되지 않음을 의미합니까?

사용자가 번호를 입력하지 않는 경우 다음 프로그램이 잘 정의되어 3있습니까?

while (true) {
 int num = ReadNumberFromConsole();
 if (num == 3)
  *((char*)NULL) = 0; //undefined behavior
}

아니면 사용자가 무엇을 입력하든 완전히 정의되지 않은 동작입니까?

또한 컴파일러는 정의되지 않은 동작이 런타임에 실행되지 않을 것이라고 가정 할 수 있습니까? 시간을 거꾸로 추론 할 수 있습니다.

int num = ReadNumberFromConsole();

if (num == 3) {
 PrintToConsole(num);
 *((char*)NULL) = 0; //undefined behavior
}

여기에서 컴파일러는 num == 3우리가 항상 정의되지 않은 동작을 호출하는 경우를 추론 할 수 있습니다. 따라서이 경우는 불가능하며 번호를 인쇄 할 필요가 없습니다. 전체 if진술을 최적화 할 수 있습니다. 표준에 따라 이러한 종류의 역 추론이 허용됩니까?



답변

주어진 프로그램에 그러한 명령문이 존재한다는 것은 전체 프로그램이 정의되지 않았거나 제어 흐름이이 명령문에 도달하면 동작이 정의되지 않음을 의미합니까?

둘 다 아닙니다. 첫 번째 조건은 너무 강하고 두 번째 조건은 너무 약합니다.

객체 액세스는 때때로 순서가 지정되지만 표준은 시간 외의 프로그램 동작을 설명합니다. Danvil은 이미 인용했습니다.

그러한 실행에 정의되지 않은 작업이 포함 된 경우이 국제 표준은 해당 입력으로 해당 프로그램을 실행하는 구현에 대한 요구 사항을 지정하지 않습니다 (정의되지 않은 첫 번째 작업 이전의 작업에 대해서도).

이것은 다음과 같이 해석 될 수 있습니다.

프로그램 실행에서 정의되지 않은 동작이 발생하면 전체 프로그램에 정의되지 않은 동작이있는 것입니다.

따라서 UB로 도달 할 수없는 문은 프로그램에 UB를 제공하지 않습니다. (입력 값으로 인해) 도달 할 수없는 도달 가능한 문은 프로그램에 UB를 제공하지 않습니다. 그것이 당신의 첫 번째 상태가 너무 강한 이유입니다.

이제 컴파일러는 일반적으로 UB가 무엇인지 알 수 없습니다. 따라서 옵티마이 저가 동작을 정의 할 경우 순서를 변경할 수있는 잠재적 UB를 사용하여 문을 다시 정렬 할 수 있도록하려면 UB가 “시간을 거슬러 올라가고”이전 시퀀스 지점 (또는 C에서) 이전에 잘못 될 수 있도록 허용해야합니다. ++ 11 용어, UB가 UB 사물 이전에 시퀀싱되는 사물에 영향을 미칩니다). 따라서 두 번째 상태가 너무 약합니다.

이에 대한 주요 예는 최적화 프로그램이 엄격한 앨리어싱에 의존하는 경우입니다. 엄격한 앨리어싱 규칙의 요점은 문제의 포인터가 동일한 메모리에 앨리어싱을 할 수있는 경우 컴파일러가 유효하게 다시 정렬 할 수없는 작업을 다시 정렬 할 수 있도록하는 것입니다. 따라서 불법 앨리어싱 포인터를 사용하고 UB가 발생하면 UB 문 “앞의”문에 쉽게 영향을 줄 수 있습니다. 추상 기계에 관한 한 UB 문은 아직 실행되지 않았습니다. 실제 개체 코드에 관한 한 부분적으로 또는 완전히 실행되었습니다. 그러나 표준은 옵티마이 저가 명령문을 다시 정렬하는 것이 무엇을 의미하는지 또는 UB에 대한 의미에 대해 자세히 설명하지 않습니다. 그것은 단지 그것이 원하는대로 잘못 될 수있는 구현 라이센스를 부여합니다.

이것을 “UB에는 타임머신이 있습니다”라고 생각할 수 있습니다.

특히 귀하의 예에 답하십시오.

  • 3을 읽은 경우에만 동작이 정의되지 않습니다.
  • 컴파일러는 기본 블록에 정의되지 않은 작업이 포함 된 경우 코드를 죽은 것으로 제거 할 수 있습니다. 기본 블록은 아니지만 모든 분기가 UB로 연결되는 경우 허용됩니다 (그리고 나는 그렇게 생각합니다). 이 예는 PrintToConsole(3)반드시 반환 할 것으로 알려진 경우 가 아니면 후보가 아닙니다 . 예외 등을 던질 수 있습니다.

두 번째와 유사한 예는 다음 -fdelete-null-pointer-checks과 같은 코드를 취할 수 있는 gcc 옵션입니다 (이 특정 예를 확인하지 않았으므로 일반적인 아이디어를 설명하는 것으로 간주).

void foo(int *p) {
    if (p) *p = 3;
    std::cout << *p << '\n';
}

다음으로 변경하십시오.

*p = 3;
std::cout << "3\n";

왜? 경우가 있기 때문에 p널 (null)이 그 다음이다를 가정 할 수 컴파일러가 따라 널 (null)와 최적화되지 않도록 코드는, 어쨌든 UB있다. 이 걸리지 리눅스 커널 ( https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2009-1897 ) 기본적으로는 널 포인터가 역 참조 모드에서 작동하기 때문에 하지 않는 가정 UB이면 커널이 처리 할 수있는 정의 된 하드웨어 예외가 발생할 것으로 예상됩니다. 최적화가 활성화되면 gcc는 -fno-delete-null-pointer-checks표준 이상 보장을 제공하기 위해를 사용해야합니다 .

추신 : “정의되지 않은 행동은 언제 발생합니까?”라는 질문에 대한 실질적인 대답 “하루를 떠날 계획을 세우기 10 분 전”입니다.


답변

1.9 / 4의 표준 상태

[참고 :이 국제 표준은 정의되지 않은 동작을 포함하는 프로그램의 동작에 대한 요구 사항을 부과하지 않습니다. — 끝 참고]

흥미로운 점은 아마도 “포함”이 의미하는 것입니다. 조금 후에 1.9 / 5에서 다음과 같이 말합니다.

그러나 그러한 실행에 정의되지 않은 작업이 포함되어있는 경우이 국제 표준은 해당 입력으로 해당 프로그램을 실행하는 구현에 대한 요구 사항을 지정하지 않습니다 (첫 번째 정의되지 않은 작업 이전의 작업에 대해서도).

여기에서는 “실행 … 해당 입력으로”를 구체적으로 언급합니다. 나는 지금 실행되지 않는 하나의 가능한 분기에서 정의되지 않은 동작이 현재 실행 분기에 영향을 미치지 않는다고 해석합니다.

그러나 다른 문제는 코드 생성 중 정의되지 않은 동작을 기반으로 한 가정입니다. 이에 대한 자세한 내용은 Steve Jessop의 답변을 참조하십시오.


답변

유익한 예는

int foo(int x)
{
    int a;
    if (x)
        return a;
    return 0;
}

현재 GCC와 현재 Clang 모두이를 최적화하여 (x86에서)

xorl %eax,%eax
ret

제어 경로 의 UB에서 항상 0 이라고 추론x 하기 때문 if (x)입니다. GCC는 초기화되지 않은 값 사용 경고도 제공하지 않습니다! (초기화되지 않은 값 경고를 생성하는 패스 이전에 위의 논리를 적용하는 패스가 실행되기 때문에)


답변

현재 C ++ 작업 초안은 1.9.4에서 다음과 같이 말합니다.

이 국제 표준은 정의되지 않은 동작을 포함하는 프로그램의 동작에 대한 요구 사항을 부과하지 않습니다.

이를 바탕으로 실행 경로에 정의되지 않은 동작을 포함하는 프로그램은 실행될 때마다 무엇이든 할 수 있습니다.

정의되지 않은 동작과 컴파일러가 일반적으로 수행하는 작업에 대한 두 가지 좋은 기사가 있습니다.


답변

“행동”이라는 단어는 무언가 수행 되고 있음을 의미 합니다 . 실행되지 않는 statemenr는 “행동”이 아닙니다.

일러스트 :

*ptr = 0;

정의되지 않은 행동입니까? ptr == nullptr프로그램 실행 중에 적어도 한 번은 100 % 확신한다고 가정 합니다. 대답은 ‘예’여야합니다.

이건 어때?

 if (ptr) *ptr = 0;

정의되지 않았습니까? ( ptr == nullptr적어도 한 번은 기억 하시나요?) 그렇지 않으면 유용한 프로그램을 전혀 작성할 수 없을 것입니다.

이 답변을 만드는 데 srandardese는 해를 입지 않았습니다.


답변

정의되지 않은 동작은 다음에 무슨 일이 발생하더라도 프로그램이 정의되지 않은 동작을 유발할 때 발생합니다. 그러나 다음 예제를 제공했습니다.

int num = ReadNumberFromConsole();

if (num == 3) {
 PrintToConsole(num);
 *((char*)NULL) = 0; //undefined behavior
}

컴파일러가의 정의를 알지 PrintToConsole못하면 if (num == 3)조건부를 제거 할 수 없습니다 . LongAndCamelCaseStdio.h다음 선언 이있는 시스템 헤더 가 있다고 가정합니다 PrintToConsole.

void PrintToConsole(int);

너무 도움이되는 건 없어요 이제이 함수의 실제 정의를 확인하여 공급 업체가 얼마나 사악한 지 (또는 그렇게 사악하지 않은, 정의되지 않은 행동이 더 나쁠 수 있음) 살펴 보겠습니다.

int printf(const char *, ...);
void exit(int);

void PrintToConsole(int num) {
    printf("%d\n", num);
    exit(0);
}

컴파일러는 실제로 컴파일러가 수행하는 작업을 모르는 임의의 함수가 종료되거나 예외를 발생시킬 수 있다고 가정해야합니다 (C ++의 경우). 호출 *((char*)NULL) = 0;후 실행이 계속되지 않기 때문에 실행되지 않을 것임을 알 수 있습니다 PrintToConsole.

정의되지 않은 동작은 PrintToConsole실제로 돌아올 때 발생 합니다. 컴파일러는 이것이 발생하지 않을 것으로 예상하므로 (이로 인해 프로그램이 정의되지 않은 동작을 실행하게되므로) 모든 일이 발생할 수 있습니다.

그러나 다른 것을 고려해 봅시다. null 검사를하고 있고 null 검사 후에 변수를 사용한다고 가정 해 보겠습니다.

int putchar(int);

const char *warning;

void lol_null_check(const char *pointer) {
    if (!pointer) {
        warning = "pointer is null";
    }
    putchar(*pointer);
}

이 경우 lol_null_checkNULL이 아닌 포인터 가 필요 하다는 것을 쉽게 알 수 있습니다. 전역 비 휘발성 warning변수에 할당하는 것은 프로그램을 종료하거나 예외를 throw 할 수있는 것이 아닙니다. 는 pointer이 마술 (가 않는 경우, 그것은 정의되지 않은 동작입니다) 함수의 중간에 그 값을 변경할 수 있도록, 또한 비 휘발성이다. 호출 lol_null_check(NULL)하면 정의되지 않은 동작이 발생하여 변수가 할당되지 않을 수 있습니다 (이 시점에서 프로그램이 정의되지 않은 동작을 실행한다는 사실이 알려져 있기 때문입니다).

그러나 정의되지 않은 동작은 프로그램이 무엇이든 할 수 있음을 의미합니다. 따라서 정의되지 않은 동작이 과거로 돌아가는 것을 막을 수는 없으며 첫 번째 행이 int main()실행 되기 전에 프로그램이 중단됩니다 . 정의되지 않은 동작이므로 말이되지 않아도됩니다. 3을 입력 한 후 충돌 할 수도 있지만 정의되지 않은 동작은 제때로 돌아가서 3을 입력하기 전에 충돌합니다. 그리고 정의되지 않은 동작이 시스템 RAM을 덮어 쓰고 2 주 후에 시스템이 충돌하게됩니다. 정의되지 않은 프로그램이 실행되지 않는 동안.


답변

프로그램이 정의되지 않은 동작을 호출하는 문에 도달하면 프로그램의 출력 / 동작에 대한 요구 사항이 없습니다. 정의되지 않은 동작이 호출되기 “전”또는 “후”발생 여부는 중요하지 않습니다.

세 가지 코드 조각 모두에 대한 귀하의 추론이 정확합니다. 특히 컴파일러는 GCC가 처리하는 방식으로 정의되지 않은 동작을 무조건 호출하는 모든 문을 처리 할 수 ​​있습니다 __builtin_unreachable(). 문에 도달 할 수 없다는 최적화 힌트 (따라서 무조건 연결되는 모든 코드 경로도 도달 할 수 없음). 물론 다른 유사한 최적화도 가능합니다.