为什么当d == 0时'd / = d'不抛出零除异常?

时间:2019-08-23 15:13:21

标签: c++ compiler-optimization undefined-behavior division divide-by-zero

我不太明白为什么我没有被零例外除:

int d = 0;
d /= d;

我希望得到除以零的异常,但会改为d == 1

为什么d /= dd == 0不会被零除?

5 个答案:

答案 0 :(得分:106)

C ++没有要捕获的“零除”异常。您观察到的行为是编译器优化的结果:

  1. 编译器假定未发生未定义行为
  2. 在C ++中零除是未定义的行为
  3. 因此,假定可以引起零除的代码不会这样做。
    • 而且,假定必须导致被零除的代码永远不会发生
  4. 因此,编译器推断出由于未发生未定义行为,因此该代码(d == 0)中未定义行为的条件一定不会发生
  5. 因此,d / d必须始终等于1。

但是...

我们可以强制编译器以零次调整代码来触发除以零的“实数”除法。

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

所以现在问题仍然存在:既然我们基本上已经迫使编译器允许这种情况发生,那会发生什么?这是未定义的行为-但是我们现在阻止了编译器针对这种未定义的行为进行优化。

通常,这取决于目标环境。这不会触发软件异常,但是它可以(取决于目标CPU)触发硬件异常(“整数除以零”),无法以传统方式捕获软件可以捕获异常。对于x86 CPU和大多数其他(但不是全部!)架构,绝对是这种情况。

但是,有一些方法可以处理硬件异常(如果发生),而不仅仅是让程序崩溃:请看一下这篇文章,了解一些可能适用的方法:Catching exception: divide by zero。请注意,它们因编译器而异。

答案 1 :(得分:37)

仅是对其他答案的补充,事实是除以零是未定义的行为,这意味着编译器在可能发生的情况下可以自由执行任何操作

  • 编译器可以假设0 / 0 == 1并进行相应的优化。这实际上是它在这里所做的。
  • 如果愿意,编译器还可以假设0 / 0 == 42并将d设置为该值。
  • 编译器还可以确定d的值是不确定的,从而使该变量未初始化,因此其值将等于先前写入为其分配的内存中的任何值。注释中在其他编译器上观察到的一些意外值可能是由这些编译器执行此类操作引起的。
  • 每当被零除时,编译器还可能决定终止程序或引发异常。因为对于该程序,编译器可以确定这将始终发生,所以它可以简单地发出代码以引发异常(或完全中止执行)​​,并将其余函数视为无法访问的代码。
  • 编译器还可以选择停止程序并开始玩纸牌游戏,而不是在被零除时引发异常。这也属于“不确定行为”的范畴。
  • 原则上,每当被零除时,编译器甚至可能发布导致计算机爆炸的代码。在C ++标准中没有什么可以禁止这种情况。 (对于某些应用,例如导弹飞行控制器,甚至可以认为这是理想的安全功能!)
  • 此外,使用标准explicitly allows undefined behavior to "time travel",以便编译器还可以在除以零之前在 上执行上述任何操作(或其他操作)。基本上,只要程序的可观察行为没有改变,该标准就允许编译器自由地对操作进行重新排序-但是,如果执行程序会导致未定义的行为,则即使最终要求也被明确放弃。因此,实际上,任何会在某个时候触发未定义行为的程序执行的 entire 行为都是未定义的!
  • 由于上述原因,编译器也可以简单地假定未发生未定义的行为,因为对于程序,在某些输入上将以未定义的方式进行行为是允许的。简单地表现就像输入的是其他东西。也就是说,即使在编译时不知道d的原始值,编译器仍可以假定它永远不会为零,并相应地优化代码。在特定的OP代码情况下,仅假设0 / 0 == 1与编译器实际上是无法区分的,但是例如,编译器还可以假设puts()中的if (d == 0) puts("About to divide by zero!"); d /= d;永远不会被执行!

答案 2 :(得分:28)

C ++标准未定义整数被零除的行为。不需要引发异常。

(浮点除以零也未定义,但IEEE754对其进行了定义。)

您的编译器正在将d /= d优化为有效的d = 1,这是一个合理的选择。可以进行此优化,因为可以假定代码中没有未定义的行为-d不可能为零。

答案 3 :(得分:1)

了解发生了什么的最简单方法是查看程序集输出

int divide(int num) {
    return num/num;
}

将为 x86-64 生成

divide(int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     eax, 1
        pop     rbp
        ret

正如你所看到的,这里没有除法运算, 但我们有mov eax, 1

这里是复制链接:https://godbolt.org/z/MbY6Wqh4T

答案 4 :(得分:0)

请注意,在这种情况下(以及其他情况下),您可以使用boost安全数字来使代码生成C ++异常。 https://github.com/boostorg/safe_numerics