programing

"정의되지 않은 행동"은 정말로 *무엇이든* 발생을 허용합니까?

kingscode 2022. 7. 15. 22:17
반응형

"정의되지 않은 행동"은 정말로 *무엇이든* 발생을 허용합니까?

"정의되지 않은 행동"의 전형적인 외설적인 예는 물론 "비음의 악마"입니다.이것은 C와 C++의 표준이 허용하는 것에 관계없이 물리적으로 불가능한 것입니다.

C와 C++ 커뮤니티는 정의되지 않은 동작의 예측 불가능성에 중점을 두는 경향이 있으며, 정의되지 않은 동작이 발생했을 때 컴파일러가 문자 그대로 프로그램을 실행하도록 허용된다는 생각을 하기 때문에 표준이 정의되지 않은 동작의 동작에 어떠한 제한도 가하지 않는다고 가정했습니다.

그러나 C++ 규격의 관련 인용문은 다음과 같습니다.

[C++14: defns.undefined]: [..] 허용 가능한 정의되지 않은 동작은 상황을 완전히 무시하고 예측할 수 없는 결과를 발생시키는 것부터 환경에 특유의 문서화된 방식으로 번역 또는 프로그램 실행 중에 동작하는 것(진단 메시지 발행 여부에 관계없이), 번역 또는 실행을 종료하는 것(진단 메시지 발행으로)까지 다양하다.c 메시지)[..]

실제로는 다음과 같은 작은 옵션세트를 지정합니다.

  • 상황을 무시합니다.- 네, 표준에서는 "예측할 수 없는 결과"라고 계속 말하고 있습니다만, 이것은 컴파일러가 코드를 삽입하는 것과는 다릅니다(코 악마의 전제 조건이라고 생각합니다).
  • 환경의 특징에 대해 문서화된 방식으로 행동하는 것은 사실 비교적 온화하게 들립니다.(코 마귀에 대한 기록은 들어본 적이 없습니다.)
  • 번역 또는 실행 종료 - 진단 기능 포함.모든 UB가 그렇게 친절하게 행동한다면?

대부분의 경우 컴파일러는 정의되지 않은 동작을 무시합니다.예를 들어 초기화되지 않은 메모리를 읽을 때 일관된 동작을 보장하기 위해 코드를 삽입하는 것은 아마도 안티 최적화일 것입니다.미정의 행동(를 들면 「시간 여행」)의 낯선 타입은, 제2의 카테고리에 속한다고 생각합니다만, 이것은 그러한 행동을 문서화하고 「환경의 특징」을 필요로 합니다(그렇기 때문에, 코의 악마는 지옥의 컴퓨터에 의해서만 생성된다고 생각됩니다).

제가 정의를 잘못 이해했나요?이것들은 포괄적인 옵션 리스트가 아니라 정의되지 않은 행동을 구성할 수 있는 단순한 예로서 의도된 것입니까?'무슨 일이든 일어날 수 있다'는 주장은 단순히 상황을 무시한 예상치 못한 부작용을 의미하는 것일까.

다음 두 가지 중요한 점을 설명하겠습니다.

  • 나는 그것이 원래의 질문에서 분명하다고 생각했고, 대부분의 사람들에게 그렇게 생각했지만, 어쨌든 나는 그것을 자세히 설명하겠다.'비악마'가 비꼬는 소리라는 건 알아
  • 구현 정의 동작이 허용하지 않는 최적화를 허용하는 방법을 설명하는 경우를 제외하고 UB가 플랫폼별 컴파일러 최적화를 허용하는 것을 설명하는 (다른) 답변을 작성하지 마십시오.

이 질문은 정의되지 않은 행동의 결점에 대한 토론의 장으로 의도된 것은 아니었지만, 그렇게 되었다.어떤 경우든, 정의되지 않은 동작이 없는 가상의 C 컴파일러에 대한스레드는 이것이 중요한 주제라고 생각하는 사람들에게는 더 큰 관심이 될 수 있습니다.

네, 무슨 일이든 일어날 수 있습니다.메모는 예시일 뿐입니다.정의는 매우 명확합니다.

정의되지 않은 행동: 본 국제표준이 요건을 부과하지 않는 행동.


자주 발생하는 혼란 지점:

'요구사항 없음'은 구현이 동작을 정의하지 않은 채로 두거나 기괴한/확정적이지 않은 작업을 수행할 필요가 없음을 이해해야 합니다.

구현은 C++ 표준에 의해 완전히 허용되며, 정상적인 동작을 문서화하고 그에 1따라 동작합니다.따라서 컴파일러가 서명된 오버플로우를 회피한다고 주장하는 경우 논리(온전성?)에 따라 해당 컴파일러의 동작에 의존해도 좋다는 메시지가 나타납니다.다른 컴파일러도 같은 동작을 할 것이라고 생각하지 마십시오.

1쳇, 심지어 한 가지를 문서화하고 다른 것을 하는 것도 허용됩니다.그건 바보 같은 짓이고, 쓰레기통에 버리게 될 거야. 왜 문서가 있는 컴파일러를 믿겠어?C++ 규격에 어긋나지 않습니다.

모든 C와 C++ 표준에서 정의되지 않은 행동의 정의는 기본적으로 표준이 발생하는 일에 대해 어떠한 요건도 부과하지 않는다는 것이다.

네, 그 말은 어떤 결과도 허용된다는 뜻입니다.그러나 일어나야 할 특별한 결과도, 일어나지 말아야 할 결과도 없습니다.정의되지 않은 동작의 특정 인스턴스에 대한 응답으로 특정 동작을 일관되게 생성하는 컴파일러와 라이브러리가 있는지는 중요하지 않습니다.- 이러한 동작은 필수가 아니며, 컴파일러의 향후 버그 수정 릴리스에서도 변경될 수 있습니다.또, 컴파일러는 C의 각 버전에 따라서도 완전하게 올바른 동작입니다.nd C++ 규격.

호스트 시스템이 콧구멍에 삽입된 프로브에 연결하는 형태로 하드웨어를 지원하는 경우 정의되지 않은 동작이 발생하면 원치 않는 비강 효과를 일으킬 수 있습니다.

정의되지 않은 동작을 통해 컴파일러는 경우에 따라 더 빠른 코드를 생성할 수 있습니다.서로 다른 두 가지 프로세서 아키텍처에 대해 생각해 봅시다.프로세서 A는 본질적으로 오버플로우 시에 반송 비트를 폐기하고 프로세서 B는 에러를 생성합니다.(물론 프로세서 C는 본질적으로 비음 악마를 발생시킵니다.이것은, 그 여분의 비트의 에너지를 not-powered nanobot으로 방출하는 가장 쉬운 방법입니다.)

표준에서 오류를 생성해야 하는 경우 프로세서A용으로 컴파일된 모든 코드는 기본적으로 추가 명령을 포함하도록 강제되며 오버플로우 체크를 수행하도록 되어 오류가 발생합니다.이로 인해 개발자가 작은 숫자만 추가할 수 있다는 것을 알더라도 코드 속도가 느려집니다.

정의되지 않은 동작은 속도를 위해 휴대성을 희생합니다.컴파일러는 '무엇이든' 발생을 허용함으로써 발생할 수 없는 상황에 대한 안전체크를 작성하는 것을 피할 수 있습니다.(혹은...)그럴 수도 있습니다.)

또한 프로그래머가 주어진 환경에서 정의되지 않은 동작이 실제로 어떤 결과를 가져올지 정확히 알고 있다면, 프로그래머는 그 지식을 자유롭게 활용하여 추가적인 성능을 얻을 수 있습니다.

코드가 모든 플랫폼에서 동일하게 동작하도록 하려면 '정의되지 않은 동작'이 발생하지 않도록 해야 합니다.다만, 이것이 목적이 아닐 수도 있습니다.

편집: (OPs 편집에 따라) 구현 정의 동작에서는 코의 악마가 일관되게 생성되어야 합니다.정의되지 않은 행동은 코 악마의 산발적인 생성을 가능하게 한다.

여기서 정의되지 않은 동작이 구현별 동작보다 더 많은 이점을 얻을 수 있습니다.특정 시스템에서 일관성이 없는 동작을 방지하기 위해 추가 코드가 필요할 수 있습니다.이 경우 정의되지 않은 동작을 통해 속도가 향상됩니다.

Undefined Behavior의 과거 목적 중 하나는 특정 액션이 플랫폼에 따라 다른 잠재적으로 유용한 영향을 미칠 수 있다는 가능성을 허용하는 것이었습니다.예를 들어, C의 초기,

int i=INT_MAX;
i++;
printf("%d",i);

일부 컴파일러는 코드가 특정 값을 인쇄하는 것을 보증하는 반면(2개의 보완 머신의 경우 일반적으로 INT_MIN) 다른 컴파일러는 프로그램이 printf에 도달하지 않고 종료되는 것을 보증한다.애플리케이션 요건에 따라서는, 어느 쪽의 동작이 도움이 되는 경우가 있습니다.동작을 정의하지 않은 채로 두는 것은 비정상적인 프로그램 종료가 오버플로의 결과로서 허용되지만 겉보기에는 유효하지만 잘못된 출력을 생성하는 애플리케이션은 오버플로를 확실하게 트랩할 수 있는 플랫폼에서 실행할 경우 오버플로를 체크하지 않을 수 있으며 오버플로의 경우 비정상적인 종료가 발생할 수 있음을 의미합니다.허용되지만 산술적으로 중복된 출력을 생성하면 오버플로우가 포착되지 않은 플랫폼에서 실행되면 오버플로우 체크가 불필요해질 수 있습니다.

그러나 최근 일부 컴파일러 저자들은 표준에 의해 존재하지 않는 코드를 누가 가장 효율적으로 제거할 수 있는지 알아보기 위해 경쟁에 뛰어든 것 같습니다.예를 들어...

#include <stdio.h>

int main(void)
{
  int ch = getchar();
  if (ch < 74)
    printf("Hey there!");
  else
    printf("%d",ch*ch*ch*ch*ch);
}

초현대적 컴파일러는 다음과 같이 결론지을 수 있다.ch74 또는 그 이상입니다.ch*ch*ch*ch*ch그러면 정의되지 않은 동작이 생성되므로 프로그램에서는 입력된 문자에 관계없이 무조건 "Hey there!"라고 출력해야 합니다.

첫째, 그것은, 정의되어 있지 않습니다는 컴파일러의 행위 정의되지 않습니다가 사용자 프로그램의 뿐만 아니라 행동이 중요하다.마찬가지로, UB 런타임에, 소스 코드 속성이 직면하지 않다.

컴파일러 라이터에게 「동작의 정의 없음」은, 「이 상황을 고려할 필요가 없다」, 또는 「이 상황을 발생시키는 소스코드는 없다고 가정할 수 있다」를 의미합니다.컴파일러는 UB를 제시하면 의도적이든 의도적이든 의도적이지 않든 무엇이든 할 수 있습니다.그래서 만약 당신이 코에 접근권을 부여했다면...

그러면 프로그램에 UB가 있는지 여부를 항상 알 수 있는 것은 아닙니다.예:

int * ptr = calculateAddress();
int i = *ptr;

이것이 UB인지 아닌지를 알기 위해서는 에서 반환되는 모든 가능한 값을 알아야 합니다.calculateAddress()(일반적인 경우에서는 불가능합니다) ("Halting Problem" 참조).컴파일러에는 다음 두 가지 선택지가 있습니다.

  • 추정하다ptr유효한 주소가 항상 지정됩니다.
  • 특정 동작을 보장하기 위해 런타임 검사 삽입

첫 번째 옵션은 빠른 프로그램을 생성하여 프로그래머에게 원치 않는 영향을 피해야 하는 부담을 주는 반면, 두 번째 옵션은 더 안전하지만 느린 코드를 생성합니다.

C 및 C++ 규격에서는 이 선택지를 열어두고 대부분의 컴파일러는 첫 번째 컴파일러를 선택하지만 Java는 두 번째 컴파일러를 요구합니다.


동작은 구현 정의되어 있지 않지만 정의되어 있지 않은 이유는 무엇입니까?

구현 정의 수단(N4296, 1.9µ2):

추상 머신의 특정 측면과 조작은 이 국제 표준에서 구현 정의(sizeof(int) 등)로 기술되어 있다.이들은 추상 기계의 매개변수를 구성합니다.각 구현에는 이러한 측면에서 특징과 동작을 설명하는 문서가 포함되어야 한다.이러한 문서에서는 해당 구현에 대응하는 추상 머신의 인스턴스(이하 "대응하는 인스턴스"라 한다)를 정의해야 합니다.

강조해 주세요.즉, 다음과 같습니다.컴파일러 라이터는, 소스코드가 실장 정의 기능을 사용하고 있는 경우에, 머신 코드의 동작을 정확하게 문서화할 필요가 있습니다.

무효 포인터가 null이 아닌 임의의 포인터에 쓰는 것은 프로그램에서 가장 예측할 수 없는 작업 중 하나이기 때문에 실행 시 체크가 필요합니다.
MMU를 사용하기 전에는 잘못된 주소에 글을 쓰는 것으로 하드웨어파괴할 수 있었습니다.이것은 코의 악마에 매우 가까운 것입니다.

트집 잡기:기준을 제시하지 않았습니다.

이것들은 C++ 표준의 초안을 생성하는 데 사용되는 소스입니다.이러한 선원은 ISO 간행물로 간주되지 않아야 하며, C++ 작업 그룹(ISO/IEC JTC1/SC22/WG21)이 공식적으로 채택하지 않는 한 이 선원에서 생성된 문서도 고려해서는 안 된다.

해석:ISO/IEC 지침 파트 2에 따르면 주석은 규범적이지 않습니다.

문서의 텍스트에 포함된 주석 및 예는 문서의 이해 또는 사용을 돕기 위한 추가 정보를 제공하기 위해만 사용해야 합니다.여기에는 요건("해야 한다"; 3.3.1과 표 H.1 참조), 또는 문서 사용에 필수적인 것으로 간주되는 정보(명제적; 표 H.1 참조), 권고("해야 한다; 3.3.2와 표 H.2 참조) 또는 허가("표 H.3 참조")가 포함되어서는 안 된다.메모는 사실의 진술로 작성할 수 있다.

강조해 주세요.이것만으로 「포괄적인 옵션 리스트」는 제외됩니다.그러나 예를 들면 "이해력을 돕기 위한 추가 정보"로 간주됩니다."를 참조해 주세요.

우주의 팽창이 어떻게 작용하는지를 설명하기 위해 풍선을 사용하는 것과 같이, "비음 악마" 밈은 문자 그대로 받아들여지는 것이 아니라는 것을 명심하세요.그것은 무엇이든 할 수 있는 상황에서 "정의되지 않은 행동"이 무엇을 해야 하는지에 대해 논하는 것은 무모하다는 것을 보여주기 위한 것이다.네, 이것은 우주에 실제 고무줄이 없다는 것을 의미합니다.

정의되지 않은 동작은 단순히 사양 작성자가 예측하지 못한 상황이 발생한 결과입니다.

신호등을 생각해 보세요.빨간색은 정지, 노란색은 빨간색에 대비, 녹색은 출발을 의미합니다.이 예에서는 자동차를 운전하는 사람이 스펙의 구현입니다.

녹색과 빨간색이 모두 켜지면 어떻게 됩니까?멈춰서서 가?빨간색이 꺼지고 녹색이 될 때까지 기다리나요?이것은 사양에 기재되어 있지 않은 경우이며, 그 결과 드라이버가 실행하는 것은 정의되지 않은 동작입니다.어떤 사람들은 한 가지 일을 할 것이고, 어떤 사람들은 다른 일을 할 것이다.무슨 일이 일어날지 장담할 수 없기 때문에 당신은 이 상황을 피하고 싶습니다.코드도 마찬가지입니다.

다른 답변은 일반적인 질문에 매우 잘 대답하기 때문에, 저는 당신의 요점 중 하나만 대답하려고 했지만, 이 부분은 언급하지 않았습니다.

"상황 무시 - 네, 표준에서는 "예측할 수 없는 결과"라고 계속 말하고 있지만, 이는 컴파일러가 코드를 삽입하는 것과 같지 않습니다(코 마귀의 전제 조건이라고 생각합니다).

컴파일러가 어떤 코드도 삽입하지 않고 센스 있는 컴파일러에서 비음 악마가 발생할 것으로 예상되는 상황은 다음과 같습니다.

if(!spawn_of_satan)
    printf("Random debug value: %i\n", *x); // oops, null pointer deference
    nasal_angels();
else
    nasal_demons();

컴파일러는 *x가 늘 포인터 참조 해제임을 증명할 수 있다면 최적화의 일부로서 "OK, 그래서 if의 이 브랜치에서 늘 포인터를 참조 해제한 것을 알 수 있습니다.그래서 저는 그 지부의 일부로서 무엇이든 할 수 있습니다.따라서 이를 최적화할 수 있습니다.

if(!spawn_of_satan)
    nasal_demons();
else
    nasal_demons();

"그리고 그 이후로는 다음과 같이 최적화할 수 있습니다."

nasal_demons();

이러한 종류의 것이 적절한 상황에서 컴파일러를 최적화하는 데 얼마나 도움이 되는지 알 수 있습니다.나는 얼마 전에 이런 종류의 사례를 최적화할 수 있는 것이 실제로 최적화에 중요한 사례들을 보았다.나중에 시간이 있을 때 파헤쳐볼 수도 있어요.

편집: 최적화에 도움이 되는 케이스의 기억 깊은 곳에서 나온 예 중 하나는 포인터가 이미 참조를 취소한 후에도 변경하지 않고 NULL(인라인 도우미 기능)인지 자주 확인하는 것입니다.최적화 컴파일러는 사용자가 참조를 취소한 것을 확인할 수 있으므로 모든 "is NULL" 체크를 최적화합니다. 참조를 취소한 경우 IS NULL 체크를 실행하지 않는 것을 포함하여 모든 것이 허용되기 때문입니다.나는 비슷한 주장이 정의되지 않은 다른 행동에도 적용된다고 믿는다.

동작을 정의하지 않은 채로 두는 이유 중 하나는 컴파일러가 최적화 시 원하는 가정을 할 수 있도록 하기 위함입니다.

최적화가 적용되어야 할 경우에 유지되어야 하는 조건이 존재하며, 그 조건이 코드의 정의되지 않은 동작에 의존한다면, 컴파일러는 어떤 방식으로든 적합 프로그램이 정의되지 않은 동작에 의존할 수 없기 때문에, 그 조건이 충족되었다고 가정할 수 있습니다.중요한 것은 컴파일러가 이러한 전제조건에 일관성을 가질 필요가 없다는 것입니다.(실장 정의 동작에는 해당되지 않음)

따라서 코드에는 다음과 같이 조작된 예가 포함되어 있다고 가정합니다.

int bar = 0;
int foo = (undefined behavior of some kind);
if (foo) {
   f();
   bar = 1;
}
if (!foo) {
   g();
   bar = 1;
}
assert(1 == bar);

컴파일러는 첫 번째 블록에서 !foo가 true이고 두 번째 블록에서 foo가 true라고 가정할 수 있으므로 코드 청크 전체를 최적화할 수 있습니다.논리적으로 foo 또는 !foo 중 하나가 true여야 합니다.따라서 코드를 보면 코드를 실행한 후에는 바가 1과 같아야 한다고 합리적으로 생각할 수 있습니다.그러나 컴파일러는 이렇게 최적화되어 있기 때문에 바는 1로 설정되지 않습니다.그리고 이제 그 주장은 거짓이 되고 프로그램은 종료됩니다. 이것은 foo가 정의되지 않은 행동에 의존하지 않았다면 일어나지 않았을 행동이다.

컴파일러가 정의되지 않은 동작을 발견하면 완전히 새로운 코드를 삽입할 수 있을까요?그렇게 하면 확실히 더 최적화할 수 있습니다.그런 일이 자주 일어날 것 같습니까?아마 아닐 거예요, 하지만 장담할 수는 없어요 그래서 코의 악마가 가능하다고 가정하고 수술하는 게 유일한 안전한 방법이에요

언급URL : https://stackoverflow.com/questions/32132574/does-undefined-behavior-really-permit-anything-to-happen

반응형