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

val*_*kov 80 c++ division divide-by-zero compiler-optimization undefined-behavior

我不太明白为什么我不能除以零例外:

int d = 0;
d /= d;
Run Code Online (Sandbox Code Playgroud)

我本来希望得到除以零的除法运算,但是反而d == 1

为什么在什么时候不d /= d将被零除d == 0

Xir*_*ema 106

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

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

然而...

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

volatile int d = 0;
d /= d; //What happens?
Run Code Online (Sandbox Code Playgroud)

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

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

但是,有一些方法可以处理硬件异常(如果发生),而不仅仅是让程序崩溃:在这篇文章中查找一些可能适用的方法:捕获异常:除以零。请注意,它们因编译器而异。

  • @Adrian这两个都很好,因为行为是不确定的。从字面上看*任何*都可以。 (24认同)
  • “在C ++中零除是未定义的行为”->请注意,编译器无法针对IEE754下的浮点类型进行此优化。必须将d设置为NaN。 (9认同)
  • @RichardHodges不允许在IEEE754下运行的编译器进行双重优化:必须生成NaN。 (6认同)
  • @formerlyknownas:这不是“优化UB”的问题-仍然是“任何事情都可能发生”的情况;只是产生1是完全有效的任何东西。获得14684554的原因一定是因为编译器优化了_甚至更进一步-它传播了初始d == 0条件,因此不仅可以得出“这是1或UB”的结论,而且实际上可以得出“这是UB,周期”的结论。因此,生成加载常数“ 1”的代码甚至不费吹灰之力。 (2认同)

Ilm*_*nen 36

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

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


Bat*_*eba 28

C ++标准未定义整数除以零的行为。这是不是需要抛出异常。

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

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

  • 编译器看到了,但可能不在乎。对于这种极端情况,已经疯狂的复杂编译器所需的额外代码复杂性可能不值得。 (6认同)
  • 重要的是要特别清楚,否则可能还会发生其他事情,哎呀,这种行为不能依靠。 (3认同)
  • 因此,如果您编写了“ int d = 0; if(d == 0)printf(“ d = 0 \ n”); d / = d;“,编译器也可以删除printf。 (3认同)
  • 当您说编译器假设“ d不可能为零”是合理的时候,您是否还假设编译器看不到以下行:`int d = 0;`?:) (2认同)