d == 0 일 때 'd / = d'가 0으로 나누기 예외를 던지지 않는 이유는 무엇입니까?


81

제로 예외로 나눗셈을 얻지 못하는 이유를 잘 모르겠습니다.

int d = 0;
d /= d;

제로 예외로 나누기를 기대했지만 대신 d == 1.

d /= d0으로 나누기 예외가 발생하지 d == 0않습니까?


25
그것은 정의되지 않은 행동입니다.
LF

51
0으로 나누는 예외는 없습니다.
πάντα ῥεῖ

15
일부 설명을 명확히하기 위해 : "0으로 나누기 예외"에 대한 메시지가 표시되면 운영 체제에서 문제가 발생했음을 알려줍니다. C ++ 예외 가 아닙니다 . C ++에서는 throw문에 의해 예외가 발생 합니다. 다른 것은 없습니다 (정의되지 않은 행동 영역에 있지 않는 한).
Pete Becker

9
C ++에는 " 제로 예외로 나누기 "와 같은 것은 없습니다 .
Algirdas Preidžius

6
@ user11659763 "이것이 정의되지 않은 동작 인 이유입니다. 대상에 완전히 의존합니다." - 그 행동의 의미를 정의되지 않은 게 아니에요 전혀 ; 설명하는 것은 구현 정의 동작 입니다. 정의되지 않은 행동은 훨씬 더 강력한 진술입니다.
marcelm

답변:


108

C ++에는 "0으로 나누기"예외가 없습니다. 관찰중인 동작은 컴파일러 최적화의 결과입니다.

  1. 컴파일러는 정의되지 않은 동작이 발생하지 않는다고 가정합니다.
  2. C ++에서 0으로 나누기는 정의되지 않은 동작입니다.
  3. 따라서 0으로 나누기를 유발할 있는 코드 는 그렇게하지 않는 것으로 추정됩니다.
    • 그리고 Division by Zero를 유발 해야하는 코드 발생하지 않는 것으로 추정됩니다.
  4. 따라서 컴파일러는 Undefined Behavior가 발생하지 않기 때문에이 코드 ( d == 0) 에서 Undefined Behavior에 대한 조건이 발생하지 않아야한다고 추론합니다.
  5. 따라서 d / d항상 1이어야합니다.

하나...

컴파일러가 코드를 약간만 변경하여 "실제"나누기를 0으로 트리거하도록 할 수 있습니다.

volatile int d = 0;
d /= d; //What happens?

이제 질문이 남아 있습니다. 기본적으로 컴파일러가이를 허용하도록 강제 했으므로 어떻게됩니까? 정의되지 않은 동작이지만 이제 컴파일러가이 정의되지 않은 동작을 최적화하는 것을 방지했습니다.

대부분 대상 환경에 따라 다릅니다. 이 소프트웨어 예외를 트리거하지 않습니다,하지만 하드웨어 예외를 트리거 (타겟 CPU에 따라 다름) (정수-으로 나누기) 소프트웨어 예외가 잡힐 수있는 전통 방식으로 잡은 할 수 없습니다. 이것은 x86 CPU와 대부분의 다른 (전부는 아닙니다!) 아키텍처의 경우에 해당합니다.

그러나 프로그램 충돌을 허용하는 대신 하드웨어 예외 (발생한 경우)를 처리하는 방법이 있습니다. 적용 가능한 일부 방법에 대해서는이 게시물을 참조하십시오. 예외 잡기 : 0으로 나누기 . 컴파일러마다 다릅니다.


25
@Adrian 행동이 정의되지 않았기 때문에 둘 다 완벽하게 괜찮습니다. 말 그대로 모든 것이 괜찮습니다.
Jesper Juhl

9
"C ++에서 0으로 나누는 것은 정의되지 않은 동작입니다."-> 컴파일러는 IEE754에서 부동 소수점 유형에 대해이 최적화를 수행 할 수 없습니다. d를 NaN으로 설정해야합니다.
Bathsheba

6
@RichardHodges IEEE754에서 작동하는 컴파일러는 double에 대한 최적화를 수행 할 수 없습니다. NaN을 생성해야합니다.
Bathsheba

2
@formerlyknownas : "UB를 최적화하는 것"의 문제가 아닙니다. 여전히 "무엇이든 일어날 수있는"경우입니다. 생산 1은 완벽하게 유효한 모든 것입니다. 14684554를 얻는 것은 컴파일러가 더욱 최적화하기 때문이어야합니다 . 초기 d==0조건을 전파 하므로 "이것은 1 또는 UB입니다"뿐만 아니라 실제로 "이것은 UB, 기간입니다"라는 결론을 내릴 수 있습니다. 따라서 상수를로드하는 코드를 생성하지 않아도됩니다 1.
hmakholm은 Monica에게

1
사람들은 최적화를 방지하기 위해 항상 휘발성을 제안하지만 휘발성 읽기 또는 쓰기를 구성하는 것은 구현에 따라 정의됩니다.
philipxy

38

다른 답변을 보완하기 위해 0으로 나누는 것이 정의되지 않은 동작이라는 사실 은 컴파일러가 어떤 일이 발생할 경우 자유롭게 할 수 있음을 의미합니다 .

  • 컴파일러는이를 가정하고 0 / 0 == 1그에 따라 최적화 할 수 있습니다 . 그것이 실제로 여기에서 한 것처럼 보입니다.
  • 컴파일러는 원하는 경우이를 가정하고 해당 값으로 0 / 0 == 42설정할 수도 d있습니다.
  • 컴파일러는 또한의 값 d이 불확정 하다고 결정할 수 있으므로 변수를 초기화되지 않은 상태로 두어 해당 값은 이전에 할당 된 메모리에 기록 된 값이됩니다. 주석의 다른 컴파일러에서 관찰 된 예상치 못한 값 중 일부는 이러한 컴파일러가 이와 같은 작업을 수행하기 때문에 발생할 수 있습니다.
  • 컴파일러는 0으로 나누기가 발생할 때마다 프로그램을 중단하거나 예외를 발생시킬 수도 있습니다. 이 프로그램의 경우 컴파일러는 이것이 항상 발생할 것이라고 판단 할 수 있기 때문에 코드를 생성하여 예외를 발생시키고 (또는 실행을 완전히 중단하고) 나머지 함수를 도달 할 수없는 코드로 처리 할 수 ​​있습니다.
  • 0으로 나누기가 발생할 때 예외를 발생시키는 대신 컴파일러는 프로그램을 중지하고 대신 솔리테어 게임을 시작할 수도 있습니다. 그것은 또한 "정의되지 않은 행동"의 우산에 속합니다.
  • 원칙적으로 컴파일러는 0으로 나누기가 발생할 때마다 컴퓨터를 폭발시키는 코드를 발행 할 수도 있습니다. C ++ 표준에는이를 금지하는 것이 없습니다. (미사일 비행 컨트롤러와 같은 특정 종류의 응용 프로그램의 경우 바람직한 안전 기능으로 간주 될 수도 있습니다!)
  • 또한 표준 은 정의되지 않은 동작을 "시간 여행"에 명시 적으로 허용 하므로 컴파일러는 0으로 나누기가 발생 하기 전에 위의 모든 작업 (또는 다른 작업)을 수행 할 수도 있습니다 . 기본적으로 표준은 컴파일러가 프로그램의 관찰 가능한 동작이 변경되지 않는 한 자유롭게 작업 순서를 변경할 수 있도록합니다. 그러나 프로그램을 실행하면 정의되지 않은 동작이 발생하면 마지막 요구 사항도 명시 적으로 포기됩니다. 따라서 실제로 어떤 시점에서 정의되지 않은 동작을 트리거하는 모든 프로그램 실행 의 전체 동작은 정의되지 않습니다!
  • 위의 결과로 컴파일러는 정의되지 않은 동작이 발생하지 않는다고 가정 할 수도 있습니다 . 일부 입력에 대해 정의되지 않은 방식으로 동작하는 프로그램에 대해 허용되는 동작 중 하나 는 입력이 무언가 있었던 것처럼 동작 하는 것입니다. 다른 . 즉,의 원래 값이 d컴파일 타임에 알려지지 않았더라도 컴파일러는 여전히 값이 0 이 아니라고 가정하고 그에 따라 코드를 최적화 할 수 있습니다. OP 코드의 특정 경우에, 이것은 단지를 가정 할 때 컴파일러와 효과적으로 구별 할 수 0 / 0 == 1없지만, 예를 들어 컴파일러는 puts()in if (d == 0) puts("About to divide by zero!"); d /= d;이 실행되지 않는다고 가정 할 수도 있습니다 !

29

정수를 0으로 나누는 동작은 C ++ 표준에서 정의되지 않습니다. 예외를 발생시킬 필요 는 없습니다 .

(부동 소수점을 0으로 나누는 것도 정의되지 않지만 IEEE754가 정의합니다.)

당신의 컴파일러는 최적화 d /= d하고 있습니다. d = 1이것은 합리적인 선택입니다. 코드에 정의되지 않은 동작이 없다고 가정 할 수 있기 때문에이 최적화를 수행 d할 수 있습니다. 이는 0 일 수 없습니다.


3
다른 일이 발생할 수도 있다는 점을 명확히하는 것이 중요합니다. IOW이 동작은 신뢰할 수 없습니다.
hyde

2
컴파일러가 "이는 d0 일 수 없습니다 "라고 가정하는 것이 합리적이라고 말할 때 컴파일러가 다음 행을 보지 않는다고 가정합니까 int d = 0;? ?? :)
Adrian Mole

6
컴파일러는 그것을 보지만 아마도 상관하지 않을 것입니다. 이와 같은 엣지 케이스에 대해 이미 미친 복잡한 컴파일러에 필요한 추가 코드 복잡성은 아마도 그만한 가치가 없을 것입니다.
user4581301

1
@ user4581301 두 가지를 함께 사용하면 감염된 분기를 감지하여 훨씬 더 많은 코드를 정리할 수 있습니다. 그래서 유용 할 것입니다.
Deduplicator

3
따라서 "int d = 0; if (d == 0) printf ("d = zero \ n "); d / = d;"를 작성하면 컴파일러도 printf를 제거 할 수 있습니다.
gnasher729

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.