정의되지 않은 동작을 포함하는 소스 코드가 컴파일러를 충돌시키는 것이 합법적입니까?


85

정의되지 않은 동작을 호출하는 잘못 작성된 C ++ 소스 코드를 컴파일한다고 가정 해 보겠습니다.

C ++ 언어 사양이 "적합한"컴파일러에서 허용되는 것으로 간주하는 관점에서 볼 때이 시나리오에서 "무엇이든"수행하면 컴파일러 충돌 (또는 내 암호 도용 또는 컴파일 타임에 오작동 또는 오류 발생)이 포함됩니다. 정의되지 않은 동작의 범위는 결과 실행 파일이 실행될 때 발생할 수있는 일로 특별히 제한됩니까?


22
"UB is UB. Live with it"... 잠깐만 요. "MCVE를 게시하십시오." ... 기다려. 나는 그것이 부적절하게 유발하는 모든 반사 신경에 대한 질문을 좋아합니다. :-)
Yunnosch

14
정말 제한이 없기 때문에 UB가 코 악마를 소환 할 수 있다고합니다 .
일부 프로그래머 친구

15
UB는 작성자가 질문 을 게시하도록 할 수 있습니다 . : P
Tanveer Badar

45
C ++ 표준이 말하는 것과 상관없이 내가 컴파일러 작성자라면 확실히 내 컴파일러의 버그로 간주 할 것입니다. 따라서 이것을 본다면 결함 보고서를 제출하십시오.
john

9
@LeifWillerts 이것은 80 년대에 돌아 왔습니다. 정확한 구조는 기억 나지 않지만 복잡한 변수 유형 사용에 달려 있다고 생각합니다. 대체품을 넣은 후 "내가 생각한대로-일이 그런 식으로 작동하지 않는다"는 순간이있었습니다. 나는 컴파일러를 비난하지 않았다. 오늘 그 컴파일러를 만나는 사람은 없을 것 같습니다. 68000 마이크로 프로세서를 대상으로하는 HP 64000 용 HP C 크로스 컴파일러였습니다.
Avi Berger

답변:


71

정의되지 않은 동작의 규범 적 정의는 다음과 같습니다.

[defns.undefined]

이 국제 표준이 요구 사항을 부과하지 않는 행동

[참고 :이 국제 표준이 행동에 대한 명시적인 정의를 생략하거나 프로그램이 잘못된 구성이나 잘못된 데이터를 사용하는 경우 정의되지 않은 동작이 예상 될 수 있습니다. 허용되지 않는 정의되지 않은 동작은 예측할 수없는 결과로 상황을 완전히 무시하는 것, 번역 또는 프로그램 실행 중에 환경 특성 (진단 메시지 발행 여부에 관계없이)의 문서화 된 방식으로 동작하는 것, 번역 또는 실행 종료 (발행 포함)에 이르기까지 다양합니다. 진단 메시지). 많은 잘못된 프로그램 구성은 정의되지 않은 동작을 발생시키지 않습니다. 그들은 진단을 받아야합니다. 상수 표현식의 평가는 정의되지 않은 것으로 명시 적으로 지정된 동작을 나타내지 않습니다. — 끝 참고]

메모 자체는 표준이 아니지만 구현으로 알려진 다양한 동작을 설명합니다. 따라서 컴파일러 충돌 (번역이 갑자기 종료 됨)은 해당 메모에 따르면 합법적입니다. 그러나 실제로 규범적인 텍스트가 말했듯이 표준은 실행이나 번역에 대한 경계를 두지 않습니다. 구현에서 암호를 훔친다고해서 표준에 명시된 계약 위반이 아닙니다.


43
즉, 실제로 컴파일러가 샌드 박싱없이 컴파일 타임에 임의의 코드를 실행하도록 할 수 있다면 다양한 보안 담당자가 이에 대해 매우 관심 이있을 것입니다. 컴파일러를 segfaulting하는 경우에도 마찬가지입니다.
Kevin

67
Kevin이 한 말도 마찬가지입니다. 이전 경력의 C / C ++ / etc 컴파일러 엔지니어로서 우리의 입장은 정의되지 않은 동작이 프로그램을 충돌시키고 출력 데이터를 망쳐 놓고 집에 불을 질 수 있다는 것이 었 습니다. 그러나 컴파일러는 입력에 관계없이 충돌하지 않아야합니다. (유용한 오류 메시지를 제공하지 않을 수도 있지만 CTHULHU TAKE THE WHEEL 및 segfaulting을 외치는 것보다 일종의 진단 및 종료를 생성해야합니다.)
Ti Strga

8
@TiStrga Cthulhu가 멋진 F1 드라이버를 만들 것이라고 장담합니다.
zeta-band

35
"구현이 암호를 훔친다고해서 표준에 명시된 계약 위반이 아닙니다." 코드에 UB가 있는지 여부에 관계없이 사실입니다. 표준은 컴파일 된 프로그램이해야 할 일만 지시합니다. 코드를 올바르게 컴파일하지만 프로세스에서 암호를 훔치는 컴파일러는 표준을 위반하지 않습니다.
Carmeister

8
@Carmeister, oooh, 좋은 지적입니다. "UB가 컴파일러에게 핵전쟁을 시작할 수있는 권한을 준다"라는 주장이 나올 때마다 사람들에게 상기시켜 드리겠습니다. 다시.
일까 추

8

NULL-deref 또는 0으로 나누기와 같이 우리가 일반적으로 걱정하는 대부분의 종류의 UB는 런타임 UB입니다. 실행 시 런타임 UB를 유발하는 함수를 컴파일 하면 컴파일러가 충돌하지 않아야합니다. 어쩌면하지 않는 한 그것은 기능 (및 기능을 통해 해당 경로)을 확실히 증명할 수있는 것입니다 프로그램에 의해 실행.

(두 번째 생각 : 아마도 컴파일 타임에 템플릿 / consistexpr에 필요한 평가를 고려하지 않았을 것입니다. 아마도 그 동안 UB는 결과 함수가 호출되지 않더라도 번역 중에 임의의 이상 함을 유발할 수 있습니다.)

번역하는 동안 행동하는 는 ISO C ++ 인용의 일부 이야기꾼의 대답 @에서는 ISO C 표준에서 사용되는 언어와 유사하다. C는 constexpr컴파일 타임에 템플릿이나 필수 평가를 포함하지 않습니다 .

그러나 재미있는 사실 : ISO C는 번역이 종료되면 진단 메시지와 함께 있어야한다는 메모에서 말합니다. 또는 "번역 중 ... 문서화 된 방식으로 행동". 나는 "상황을 완전히 무시하는 것"이 ​​번역 중단을 포함하는 것으로 읽을 수 있다고 생각하지 않습니다.


번역 시간 UB에 대해 배우기 전에 작성된 오래된 답변. 하지만 런타임 -UB의 경우에는 사실이므로 잠재적으로 여전히 유용합니다.


컴파일 타임에 발생 하는 UB 같은 것은 없습니다 . 그것은 일 수 실행의 특정 경로를 따라 컴파일러 있지만, C ++ 용어는되지 일어난 실행에 도달 할 때까지 함수를 통해 실행 경로있다.

컴파일조차 불가능하게 만드는 프로그램의 결함은 UB가 아니라 구문 오류입니다. 이러한 프로그램은 C ++ 용어로 "잘 구성되지 않았습니다"(내 표준이 올바른 경우). 프로그램은 잘 구성 될 수 있지만 UB를 포함합니다. 정의되지 않은 동작과 잘못된 형식의 차이, 진단 메시지가 필요하지 않음

내가 뭔가를 오해하지 않는 한 ISO C ++ 에서는 이 프로그램이 올바르게 컴파일되고 실행되도록 요구 합니다. 실행은 0으로 나누기에 도달하지 않기 때문입니다. (실제로 ( Godbolt ) 좋은 컴파일러는 실행 가능한 파일을 만듭니다. gcc / clang은 x / 0최적화 할 때도 경고 하지만 이것 에 대해서는 경고 하지 않습니다. 어쨌든 우리는 낮은 ISO C ++가 구현 품질을 허용 하는 정도 를 알려줍니다 . 따라서 gcc를 확인합니다. / clang은 내가 프로그램을 올바르게 작성했는지 확인하는 것 외에는 거의 유용한 테스트가 아닙니다.)

int cause_UB() {
    int x=0;
    return 1 / x;      // UB if ever reached.
 // Note I'm avoiding  x/0  in case that counts as translation time UB.
 // UB still obvious when optimizing across statements, though.
}

int main(){
    if (0)
        cause_UB();
}

이에 대한 유스 케이스는 C 전 처리기 또는 constexpr변수 및 해당 변수에 대한 분기를 포함 할 수 있으며, 이는 이러한 상수 선택에 도달하지 않은 일부 경로에서 말도 안되는 결과를 초래합니다.

컴파일 타임에 보이는 UB를 유발하는 실행 경로는 절대 취하지 않는다고 가정 할 수 있습니다. 예를 들어 x86 용 컴파일러 ud2cause_UB(). 또는 함수 내에서 한 쪽이 증명 가능한 UB 로 if()이어지는 경우 분기를 제거 할 수 있습니다.

그러나 컴파일러는 여전히 모든 컴파일하는 다른 건전하고 올바른 방법입니다. 모든 경로 하지 않는 만남 (또는 만남을 입증 할 수 없음) UB는 여전히 실행하는대로-경우 C ++ 추상 기계가 실행되었다 ASM로 컴파일해야합니다.


무조건 컴파일 시간에 표시되는 UB in main은이 규칙의 예외 라고 주장 할 수 있습니다 . 또는에서 시작하는 실행 main이 실제로 보장 된 UB에 도달한다는 것을 컴파일시 증명할 수 있습니다.

나는 아직도 법적 컴파일러 행동이 수류탄이 폭발 생산 등이 있다고 주장 거라고 경우 실행. 또는 더 그럴듯하게 그 정의 main는 하나의 불법 명령으로 구성됩니다. 나는 당신이 경우 주장 거라고 결코 프로그램을 실행하지, 아직 UB가되지 않았습니다. 컴파일러 자체는 폭발 할 수 없습니다, IMO.


브랜치 내부에 가능하거나 입증 가능한 UB를 포함하는 함수

주어진 실행 경로에있는 UB는 이전의 모든 코드를 "오염"시키기 위해 시간이 거꾸로 도달합니다. 그러나 실제로 컴파일러는 실행 경로가 컴파일시 표시되는 UB로 이어지는 것을 실제로 증명할 수있을 때만 해당 규칙을 활용할 수 있습니다. 예 :

int minefield(int x) {
    if (x == 3) {
        *(char*)nullptr = x/0;
    }

    return x * 5;
}

컴파일러는 INT_MIN 및 INT_MAX에서 부호있는 오버플로 UB xx * 5발생 하는 지점까지 3 개 이외의 모든 항목에서 작동하는 asm을 만들어야 합니다. 이 함수가으로 호출되지 않으면 x==3프로그램은 물론 UB를 포함하지 않으며 작성된대로 작동해야합니다.

if(x == 3) __builtin_unreachable();컴파일러에게 x확실히 3이 아님 을 알리기 위해 GNU C로 작성했을 수도 있습니다 .

실제로는 일반 프로그램의 모든 곳에 "지뢰밭"코드가 있습니다. 예를 들어 정수로 나누면 컴파일러에게 0이 아님을 약속합니다. 모든 포인터 deref는 컴파일러에게 NULL이 아님을 약속합니다.


3

여기서 "법적"이란 무엇을 의미합니까? 이러한 표준에 따르면 C 표준 또는 C ++ 표준과 모순되지 않는 모든 것은 합법적입니다. 성명서를 실행하고 i = i++;결과적으로 공룡이 세계를 장악한다면 표준에 위배되지 않습니다. 그러나 그것은 물리학의 법칙과 모순되므로 일어나지 않을 것입니다 :-)

정의되지 않은 동작이 컴파일러와 충돌하더라도 C 또는 C ++ 표준을 위반하지 않습니다. 그러나 이것은 컴파일러의 품질이 향상 될 수 있다는 것을 의미합니다.

이전 버전의 C 표준에는 오류이거나 정의되지 않은 동작에 의존하지 않는 문이있었습니다.

char* p = 1 / 0;

char *에 상수 0을 할당 할 수 있습니다. 0이 아닌 상수를 허용하는 것은 아닙니다. 1 / 0의 값은 정의되지 않은 동작이므로 컴파일러가이 명령문을 수락해야하는지 여부는 정의되지 않은 동작입니다. (오늘날 1/0은 더 이상 "정수 상수 표현"의 정의를 충족하지 않습니다.)


3
정확히 말하면, 공룡이 세계를 점령하는 것은 물리 법칙 (예 : 쥬라기 공원 변형)과 모순되지 않습니다. 그럴 가능성은 거의 없습니다. :)
괴물
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.