在数学上,0 / 0
未定义。但是,在C编程中(特别是在ubuntu上的gcc
编译器gcc-7.3.0),它产生的答案为1
。我想知道原因。它是通过重复减法工作的吗?对于相同的代码,如果我使用n = 1 / n
,我会得到一个浮点异常。但是,对于gcc-5.3.0,它会产生错误。
#include <stdio.h>
int main() {
int n = 5;
n = n - 5;
switch (n) {
case 0:
printf("n= %d", n);
n = n / n;
printf("n calc= %d", n);
break;
case 5:
printf("n=5");
break;
default:
printf("n=1");
break;
}
return 0;
}
除以零是C编程中未定义的行为(C116.5.5§6)。这意味着没有可预测的结果,也无法说明您的程序可能会做什么。
尝试理由为什么你得到“任何可能发生的事情”的特定结果并不是很有意义。它可能打印1,它可能打印42,它可能根本不打印。该程序可能会崩溃并烧毁。如果您运行程序两次,您可能会得到不同的结果。等等。
至于为什么不会出现编译器错误,编译器无法或不必查找或诊断运行时错误。程序员需要避免大多数未定义行为的情况。
如果使用纯整型常量表达式(如int x = 0/0;
),一些好的编译器可能会发出警告,但同样不是编译器的工作就是找到这个bug。这是程序员的工作。因此,您应该养成在应用操作数之前始终检查/
或%
的右操作数是否为零的习惯。
允许编译器假定代码不会调用未定义的行为,并在优化代码时使用该假设。
对于任何值,除了n==0
之外,表达式n/n
的计算结果为1.由于在零中潜水是C中的未定义行为,编译器可以假设这种情况永远不会发生,即它可以假设n!=0
。由于它可以假设,编译器可以用n/n
的廉价常数值替换慢1
。这可能与所讨论的代码有关。
当然,编译器不需要做出任何这样的假设,并且可能产生实际的划分。取决于执行代码的硬件,在被除零的情况下可能触发特定硬件信号(或异常)。我怀疑任何合理的编译器都会在此示例中为优化代码发出除法,因为除法非常慢,并且根据标准,常量1
足够好。但是,它仍然可能在调试模式下产生实际划分。
在优化其他未定义的行为时会发生类似的事情,例如int
溢出:
void f(int x)
{
if (x > x + 1)
// executed only in case of overflow,
// which is undefined behavior,
// and hence likely optimized out.
overflow_error();
...
不要依赖未定义的行为 - 这是不可预测的。
从Godbolt's compiler explorer可以看出,gcc
和clang
优化n / n
为n
整数,以评估1
没有任何警告。 gcc
甚至在-O0
执行此优化,而clang
生成-O0
的分区操作码,但-O1
及以上的优化替代品。
0 / 0
无论如何都有未定义的行为,因此如果n / n
为零,n
的任何行为都可以,包括评估1
而没有可观察到的副作用。
如果你坚持,你可以将n
限定为volatile
,并且编译器可能会生成一个除法,因为gcc
和clang
在这种情况下都会这样做,但只要n
被读取两次,它就不必。