当 d = = 0时,为什么’d/= d’不抛出除以0的异常?

我不太明白为什么我没有得到除以零的例外:

int d = 0;
d /= d;

我希望得到一个除以零的例外,但是却得到了 d == 1

为什么 d /= dd == 0时不抛除零例外?

6617 次浏览

整数除以零的行为没有被 C + + 标准定义。抛出异常需要 没有

(浮点除以零也没有定义,但 IEEE754定义了它。)

你的编译器正在优化 d /= d到,有效的 d = 1,这是一个合理的选择。它允许进行这种优化,因为它允许假设在您的代码中没有未定义的行为-即 d不可能为零。

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

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

但是..。

我们可以强制编译器触发一个“真正的”除零与一个小的调整你的代码。

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

所以现在的问题是: 现在我们基本上已经强制编译器允许这种情况发生,会发生什么呢?这是未定义的行为ーー但我们现在已经阻止了编译器围绕这个未定义行为进行优化。

主要取决于目标环境。这不会触发软件异常,但是它 可以(取决于目标 CPU)触发硬件异常(Integer-Divide-by-Zero) ,这不能以传统方式捕获软件异常。对于 x86 CPU 和大多数其他 CPU (但不是全部)来说,情况肯定是这样的建筑。

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

为了补充其他答案,除以零的事实是未定义行为的,这意味着在发生这种情况的情况下:

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

注意,在这种情况下(和其他情况下) ,可以通过使用升级安全数字让代码生成 C + + 异常。https://github.com/boostorg/safe_numerics

了解所发生情况的最简单方法是查看程序集输出

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