C - gcc-7或更高版本中的0/0

问题描述 投票:0回答:3

在数学上,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 debugging
3个回答
4
投票

除以零是C编程中未定义的行为(C116.5.5§6)。这意味着没有可预测的结果,也无法说明您的程序可能会做什么。

尝试理由为什么你得到“任何可能发生的事情”的特定结果并不是很有意义。它可能打印1,它可能打印42,它可能根本不打印。该程序可能会崩溃并烧毁。如果您运行程序两次,您可能会得到不同的结果。等等。

至于为什么不会出现编译器错误,编译器无法或不必查找或诊断运行时错误。程序员需要避免大多数未定义行为的情况。

如果使用纯整型常量表达式(如int x = 0/0;),一些好的编译器可能会发出警告,但同样不是编译器的工作就是找到这个bug。这是程序员的工作。因此,您应该养成在应用操作数之前始终检查/%的右操作数是否为零的习惯。


2
投票

允许编译器假定代码不会调用未定义的行为,并在优化代码时使用该假设。

对于任何值,除了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();
   ...

不要依赖未定义的行为 - 这是不可预测的。


2
投票

Godbolt's compiler explorer可以看出,gccclang优化n / nn整数,以评估1没有任何警告。 gcc甚至在-O0执行此优化,而clang生成-O0的分区操作码,但-O1及以上的优化替代品。

0 / 0无论如何都有未定义的行为,因此如果n / n为零,n的任何行为都可以,包括评估1而没有可观察到的副作用。

如果你坚持,你可以将n限定为volatile,并且编译器可能会生成一个除法,因为gccclang在这种情况下都会这样做,但只要n被读取两次,它就不必。

© www.soinside.com 2019 - 2024. All rights reserved.