정의되지 않은 동작이있는 분기를 도달 할 수 없다고 가정하고 데드 코드로 최적화 할 수 있습니까?


88

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

*((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진술을 최적화 할 수 있습니다. 표준에 따라 이러한 종류의 역 추론이 허용됩니까?


19
가끔 있기 때문에 담당자의 많은 사용자가 질문에 대한 자세한 upvotes을 얻을 궁금 "오, 그들이 담당자를 많이 가지고, 이것은 좋은 질문해야"...하지만이 경우에 나는 질문을 읽고 와우, 이것이 "생각 좋은 나는 질문자를보기도 전에.
turbulencetoo

4
나는 시간이 있다고 생각 정의되지 않은 행동이 나타난다는 정의되지 않습니다.
eerorika 2014

6
C ++ 표준은 어떤 지점에서든 동작이 정의되지 않은 실행 경로가 완전히 정의되지 않는다고 명시 적으로 말합니다. 나는 경로에서 정의되지 않은 동작을 가진 모든 프로그램이 완전히 정의되지 않았다고 해석 할 수도 있습니다 (다른 부분에 대한 합리적인 결과를 포함하지만 보장되지는 않음). 컴파일러는 정의되지 않은 동작을 자유롭게 사용하여 프로그램을 수정할 수 있습니다. blog.llvm.org/2011/05/what-every-c-programmer-should-know.html 에는 몇 가지 좋은 예가 포함되어 있습니다.
Jens

4
@Jens : 실제로 실행 경로를 의미합니다. 그렇지 않으면 문제가 발생 const int i = 0; if (i) 5/i;합니다.
MSalters 2014

1
일반적으로 컴파일러는 그것이 PrintToConsole호출 되지 않는다는 것을 증명할 수 없으므로 호출을 std::exit해야합니다.
MSalters 2014

답변:


65

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

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

객체 액세스는 때때로 순서가 지정되지만 표준은 시간 외의 프로그램 동작을 설명합니다. 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 분 전"입니다.


4
사실 과거에는 이로 인해 보안 문제가 꽤 많았습니다. 특히 사후 오버플로 검사는 이로 인해 최적화 될 위험이 있습니다. 예를 들어 void can_add(int x) { if (x + 100 < x) complain(); }경우 때문에, 완전히 멀리 최적화 할 수 있습니다 x+100 아무튼 ' 오버플로 아무 일도 발생하지 않으며, 경우 x+100 않는 것도 있으므로, UB 표준에 따라입니다 오버 플로우, 발생하지합니다.
fgp 2014-04-18

3
@fgp : 맞아요. 컴파일러가 당신을 처벌하기 위해 의도적으로 코드를 깨뜨리는 것처럼 느끼기 시작하기 때문에 사람들이 넘어지면 씁쓸하게 불평하는 최적화입니다. "당신이 그것을 제거하기를 원한다면 왜 내가 그렇게 썼을까요!" ;-)하지만 때로는 더 큰 산술 표현식을 조작 할 때 최적화기에 유용하다고 생각합니다. 오버플로가 없다고 가정하고 이러한 경우에만 필요한 값 비싼 것을 피하는 것입니다.
Steve Jessop 2014

2
사용자가 3을 입력하지 않으면 프로그램이 정의되지 않은 것이 아니라 실행 중에 3을 입력하면 전체 실행이 정의되지 않는다고 말하는 것이 맞습니까? 프로그램이 정의되지 않은 동작을 호출하는 것이 100 % 확실하면 (그리고 그보다 빨리) 동작이 허용됩니다. 내 진술이 100 % 정확합니까?
usr

3
@usr : 그렇습니다. 귀하의 특정 예 (및 처리되는 데이터의 불가피성에 대한 몇 가지 가정)를 사용하면 구현이 원칙적으로 버퍼링 된 STDIN에서 3원하는 경우 미리 보고 하나를 보자 마자 하루 동안 집에서 포장 할 수 있다고 생각합니다. 들어오는.
Steve Jessop 2014

3
PS에 대한 추가 +1 (할 수있는 경우)
Fred Larson

10

1.9 / 4의 표준 상태

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

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

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

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

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


1
문자 그대로 취하면 존재하는 모든 실제 프로그램에 대한 사형 선고입니다.
usr

6
나는 코드가 실제로 도달 하기 전에 UB가 나타날 수 있는지에 대한 질문이라고 생각하지 않습니다 . 내가 이해했듯이 질문은 코드에 도달하지 않으면 UB가 나타날 수 있는지 여부였습니다. 물론 그것에 대한 대답은 "아니오"입니다.
sepp2k 2014

표준은 1.9 / 4에서 이것에 대해 명확하지 않지만 1.9 / 5는 당신이 말한 것으로 해석 될 수 있습니다.
Danvil

1
메모는 비표준입니다. 1.9 / 5는 1.9 / 4의 노트를 능가
MSalters 2014

5

유익한 예는

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는 초기화되지 않은 값 사용 경고도 제공하지 않습니다! (초기화되지 않은 값 경고를 생성하는 패스 이전에 위의 논리를 적용하는 패스가 실행되기 때문에)


1
흥미로운 예. 최적화를 활성화하면 경고가 숨겨지는 것은 다소 불쾌합니다. 이것은 문서화되지도 않았습니다. GCC 문서는 최적화를 활성화하면 더 많은 경고가 발생 한다고 만 말합니다 .
sleske 2014

@sleske 그것은 끔찍합니다. 동의하지만 초기화되지 않은 값 경고는 "올바르게하기"로 악명이 높습니다. 완벽하게 수행하는 것은 Halting Problem과 동일하며 프로그래머는 "불필요한"변수 초기화를 추가하여 잘못된 긍정을 억제하는 것에 대해 이상하게 비합리적입니다. 따라서 컴파일러 작성자는 배럴을 넘어서게됩니다. 저는 GCC를 해킹하곤했는데 모두가 초기화되지 않은 가치 경고 패스를 엉망으로 만드는 것을 두려워했던 것을 기억합니다.
zwol 2014 년

@zwol : 이러한 데드 코드 제거로 인한 "최적화"가 실제로 유용한 코드를 얼마나 작게 만들고 결국 프로그래머가 코드를 더 크게 만드는지 궁금합니다 (예 : a모든 상황에서 초기화 할 코드 추가) 초기화되지 a않으면 함수가 아무것도하지 않는 함수에 전달 될 것입니다)?
supercat 2015-04-23

@supercat 나는 ~ 10 년 동안 컴파일러 작업에 깊이 관여하지 않았고, 장난감 예제에서 최적화에 대해 추론하는 것은 거의 불가능합니다. 이러한 유형 의 최적화는 내가 올바르게 기억한다면 실제 애플리케이션에서 전체 코드 크기를 2-5 % 감소시키는 경향이 있습니다.
zwol 2015

1
@supercat 2-5 %는 이러한 일 이 진행됨에 따라 거대 합니다. 사람들이 0.1 % 땀을 흘리는 것을 보았습니다.
zwol 2015

4

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

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

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

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


1
말이 안 돼. 물론 정의되지 않은 int f(int x) { if (x > 0) return 100/x; else return 100; }경우에도이 함수 는 정의되지 않은 동작을 호출하지 100/0않습니다.
fgp 2014

1
@fgp 표준 (특히 1.9 / 5)이 말하는 것은 정의되지 않은 동작에 도달 할 수 있다면 언제 도달하는지는 중요하지 않다는 것입니다. 예를 들어, 아무것도 인쇄 printf("Hello, World"); *((char*)NULL) = 0 되지 않을 수도 있습니다. 이는 컴파일러가 정의되지 않은 동작을 고려하지 않고도 결국 발생할 것이라는 것을 알고있는 작업 (물론 종속성 제약 조건에 따라)을 자유롭게 재정렬 할 수 있기 때문에 최적화에 도움이됩니다.
fgp 2014-04-18

100/0이 평가 될 입력이 없기 때문에 함수가있는 프로그램에는 정의되지 않은 동작이 포함되어 있지 않습니다.
Jens

1
정확합니다. 따라서 중요한 것은 UB가 실제로 트리거 될 수 있는지 여부가 아니라 이론적 으로 트리거 될 수 있는지 여부 입니다. 또는 int x,y; std::cin >> x >> y; std::cout << (x+y);"1 + 1 = 17"이라고 말할 수 있다고 주장 할 준비가 되었습니까? 오버플로 가 발생하는 일부 입력 x+y( int부호 된 유형 이므로 UB)이 있기 때문 입니다.
fgp 2014

공식적으로 나는 프로그램을 트리거하는 입력이 있기 때문에 프로그램에 정의되지 않은 동작이 있다고 말할 수 있습니다. 그러나 정의되지 않은 동작없이 프로그램을 작성하는 것은 불가능하기 때문에 이것이 C ++의 맥락에서 의미가 없다는 것은 맞습니다. C ++에서 정의되지 않은 동작이 적을 때 원하지만 언어가 작동하는 방식이 아닙니다 (그리고 이에 대한 몇 가지 좋은 이유가 있지만 일상적인 사용과는 상관이 없습니다 ...).
Jens

3

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

일러스트 :

*ptr = 0;

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

이건 어때?

 if (ptr) *ptr = 0;

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

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


3

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

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 주 후에 시스템이 충돌하게됩니다. 정의되지 않은 프로그램이 실행되지 않는 동안.


모든 유효한 포인트. PrintToConsole충돌 후에도 볼 수 있고 강력하게 시퀀스되는 프로그램 외부 부작용을 삽입하려는 시도입니다. 이 문장이 최적화되었는지 확실히 알 수있는 상황을 만들고 싶었습니다. 그러나 그것이 결코 돌아 오지 않을 수도 있다는 점에서 당신이 옳습니다.; 글로벌에 쓰는 예는 UB와 관련이없는 다른 최적화의 대상이 될 수 있습니다. 예를 들어 사용하지 않는 전역을 삭제할 수 있습니다. 제어권을 반환하는 방식으로 외부 부작용을 만드는 아이디어가 있습니까?
usr

외부에서 관찰 할 수있는 부작용이 컴파일러가 자유롭게 반환을 가정 할 수있는 코드에 의해 생성 될 수 있습니까? 내 이해에 따르면 단순히 volatile변수 를 읽는 메서드조차도 I / O 작업을 합법적으로 트리거하여 현재 스레드를 즉시 중단 할 수 있습니다. 인터럽트 처리기는 다른 작업을 수행하기 전에 스레드를 종료 할 수 있습니다. 컴파일러가 그 시점 이전에 정의되지 않은 동작을 푸시 할 수 있다는 근거가 없습니다.
supercat 2014-07-05

C 표준의 관점에서 볼 때 Undefined Behavior를 사용하면 컴퓨터가 프로그램의 이전 작업에 대한 모든 증거를 추적하고 파괴하는 일부 사람들에게 메시지를 보내는 것은 불법이 아니지만 작업이 스레드를 종료 할 수 있다면 그 행동 이전에 시퀀싱 된 모든 것은 그 이후에 발생한 정의되지 않은 행동보다 먼저 발생해야합니다.
supercat 2014-07-05

1

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

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


1
호기심에서 언제 __builtin_unreachable()부터 시간의 앞뒤로 진행되는 효과를 시작 했 습니까? extern volatile uint32_t RESET_TRIGGER; void RESET(void) { RESET_TRIGGER = 0xAA55; __memorybarrier(); __builtin_unreachable(); }내가 builtin_unreachable()컴파일러에게 return명령어를 생략 할 수 있음을 알리는 것이 좋다고 볼 수 있다는 것을 감안할 때 , 그것은 앞의 코드를 생략 할 수 있다고 말하는 것과는 다소 다릅니다.
supercat 2015-04-25

@supercat RESET_TRIGGER는 휘발성이므로 해당 위치에 대한 쓰기는 임의의 부작용을 가질 수 있습니다. 컴파일러에게는 불투명 한 메서드 호출과 같습니다. 따라서 __builtin_unreachable도달 한 것은 증명할 수 없습니다 (사례가 아닙니다) . 이 프로그램이 정의되었습니다.
usr

@usr : 저수준 컴파일러는 휘발성 액세스를 불투명 한 메서드 호출로 처리해야한다고 생각하지만 clang도 gcc도 그렇게하지 않습니다. 무엇보다도 불투명 한 메서드 호출은 주소가 외부 세계에 노출되고 라이브 restrict포인터에 의해 액세스되지 않았거나 액세스되지 않을 모든 개체의 모든 바이트 가 unsigned char*.
supercat

@usr : 컴파일러가 노출 된 개체에 대한 액세스와 관련하여 휘발성 액세스를 불투명 한 메서드 호출로 취급하지 않는 경우 다른 목적으로 그렇게 할 것이라고 예상 할 특별한 이유가 없습니다. 컴파일러가 휘발성 액세스의 모든 가능한 효과를 알 수있는 일부 하드웨어 플랫폼이 있기 때문에 표준은 구현에서 그렇게 할 것을 요구하지 않습니다. 그러나 임베디드 사용에 적합한 컴파일러는 휘발성 액세스가 컴파일러가 작성 될 때 발명되지 않은 하드웨어를 트리거 할 수 있음을 인식해야합니다.
supercat

@supercat 나는 당신이 옳다고 생각합니다. 휘발성 작업은 "추상 시스템에 영향을 미치지 않음"으로 보이므로 프로그램을 종료하거나 부작용을 일으킬 수 없습니다.
usr

1

많은 종류의 표준에 대한 많은 표준은 IETF RFC 2119에 정의 된 것과 유사한 명명법을 사용하여 구현이 수행해야하거나 수행하지 않아야하는 작업을 설명하는 데 많은 노력을 기울입니다 (해당 문서의 정의를 반드시 인용하지는 않음). 많은 경우에, 일에 대한 설명은 구현해야한다고 그들이 쓸모없는 것 또는 비현실적 어디에 경우를 제외 되는 요건보다 더 중요 모두 부합하는 구현을 준수해야합니다.

안타깝게도 C 및 C ++ 표준은 100 % 필요하지는 않지만 반대되는 동작을 문서화하지 않는 품질 구현에 대해 예상되어야하는 사항에 대한 설명을 피하는 경향이 있습니다. 구현이 무언가를해야한다는 제안은 열등하지 않은 행동을 암시하는 것으로 보일 수 있으며, 주어진 구현에서 어떤 행동이 유용하거나 실용적 일지, 비실용적이고 쓸모가 없는지 일반적으로 명백한 경우에는 표준이 그러한 판단을 방해 할 필요성을 거의 인식하지 못했습니다.

영리한 컴파일러는 코드가 불가피하게 정의되지 않은 동작을 유발하는 입력을받는 경우를 제외하고는 아무런 영향을 미치지 않는 코드를 제거하면서 표준을 준수 할 수 있지만 "영리한"과 "멍청한"은 반대말이 아닙니다. 표준의 저자가 주어진 상황에서 유용하게 행동하는 것이 쓸모없고 비실용적 인 어떤 종류의 구현이있을 수 있다고 결정했다는 사실은 그러한 행동이 다른 사람에게 실용적이고 유용한 것으로 간주되어야하는지에 대한 판단을 의미하지 않습니다. 구현이 "데드 브랜치"프 루닝 기회의 손실을 초과하는 비용없이 행동 보장을 유지할 수 있다면, 그 보증으로부터 사용자 코드가받을 수있는 거의 모든 가치는 제공 비용을 초과 할 것입니다. 데드 브랜치 제거는 다음과 같은 경우에 괜찮을 수 있습니다.주어진 상황 사용자 코드에서 거의 모든 가능한 행동을 취급 한 수 있다면,하지만 다른 막 다른 지점 제거 이상을, 어떤 노력 사용자 코드 UB 가능성이 DBE에서 달성 된 값을 초과하지 않도록 소비해야합니다.


UB를 피 하면 사용자 코드에 비용이 발생할 수 있다는 점이 좋습니다 .
usr

@usr : 모더니스트들이 완전히 놓친 지점입니다. 예를 추가해야합니까? 예를 들어 코드가 오버플로되지 않는 x*y < z시기 를 평가해야하고 x*y오버플로의 경우 임의의 방식으로 0 또는 1을 산출하지만 부작용이없는 경우 대부분의 플랫폼에서 두 번째 및 세 번째 요구 사항을 충족하는 것이 더 비싸야하는 이유가 없습니다. 첫 번째를 충족하지만 모든 경우에 표준 정의 동작을 보장하기 위해 표현식을 작성하는 방법은 경우에 따라 상당한 비용을 추가합니다. (int64_t)x*y < z계산 비용을 4 배 이상 늘릴 수 있는 표현식 작성 ...
supercat

... 어떤 플랫폼에서, 그리고로 작성하는 것은 (int)((unsigned)x*y) < z이 있음을 알고 있다면 예를 들어, (그렇지 않으면 유용한 대수 대체를되었을 수 있습니다 무엇을 사용에서 컴파일러를 방지 할 수 xz동일하고, 긍정적는 원래 표현을 단순화 할 수 y<0있지만, 버전이 서명 사용 컴파일러가 곱하기를 수행하도록 강제합니다). 표준이 요구하지 않는데도 컴파일러가 보장 할 수 있다면 "부작용없이 0 또는 1 수율"요구 사항을 유지할 것이며, 사용자 코드는 다른 방법으로는 얻을 수 없었던 컴파일러 최적화 기회를 제공 할 수 있습니다.
supercat

예, 좀 더 온화한 형태의 정의되지 않은 행동이 여기에 도움이 될 것 같습니다. 프로그래머 x*y는 오버플로의 경우 정상 값을 방출하지만 모든 값을 방출 하는 모드를 켤 수 있습니다 . C / C ++로 구성 가능한 UB가 중요해 보입니다.
usr

@usr : C89 표준의 작성자가 서명되지 않은 짧은 값을 로그인하도록 승격하는 것이 가장 심각한 주요 변경 사항이며 무지한 바보가 아니라고 말한 사실에 따르면 플랫폼이 유용한 행동 보장을 정의하고, 그러한 플랫폼에 대한 구현은 프로그래머에게 이러한 보장을 제공하고 프로그래머는이를 악용하고 있었으며, 그러한 플랫폼의 컴파일러 는 표준이 명령하든 말든 그러한 행동 보장을 계속 제공 할 것 입니다.
supercat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.