如何同时满足 (i + 1) < ii 和 (i + 1) > ii 的真实性?

6

我目前正在学习C语言编程,但是我遇到了一些奇怪的行为。我期望只输出一个结果,但实际上却输出了两个结果,如下所示:

$ ./a.out
yes1 0x80000000
yes3 0x80000000

怎么可能呢?
我无法理解这个结果。

OS : x86_64 Ubuntu Linux
C compiler : gcc (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1

gcc -O2 weird.c

#include <stdio.h>

int main() {

    int i = 0x7fffffff;
    int ii = 0x0000000f;

    if ((i + 1) < ii)
        printf ("yes1 %#x\n", i + 1);

    if ((i + 1) == ii)
        printf ("yes2 %#x\n", i + 1);

    if ((i + 1) > ii)
        printf ("yes3 %#x\n", i + 1);

    return 0;
}

1
0x7fffffff + 1 = 0x80000000 - user3414693
gcc(Ubuntu / Linaro 4.7.2-2ubuntu1)4.7.2可以(在32位系统上)正确地执行它 - Ferenc Deak
它已被删除,但您最初有C++标签,您是否关心C++? - Shafik Yaghmour
@ShafikYaghmour,你说得对! - Ferenc Deak
3个回答

7
在您的情况下,(i+1)超出了整数变量的范围。事实上,在ANSI标准中,有符号int变量的溢出是未定义的行为,因此严格来说可能导致任何结果。您的编译器可能符合标准,但任何具有良好计算机理解能力的人都会预期该变量将溢出为负值,因为计算机寄存器不区分有符号/无符号范围。以下是ANSI标准关于"未定义行为"(以及其他情况)的说明:在以下情况下的行为是未定义的:
- 算术运算无效(例如除以0或取模),或者产生的结果不能在提供的空间中表示(例如溢出或下溢)(3.3)。
另一方面,这对于无符号类型无效:
- 包括无符号操作数的计算永远不会溢出,因为不能由结果无符号整数类型表示的结果会对比结果无符号整数类型可以表示的最大值多1进行减少。
以下是引用部分(3.3表达式)的相关部分:
- 如果在表达式求值过程中发生异常(即结果在数学上未定义或不可表示),则行为是未定义的。

可以设置-fwrapv,这会使溢出完全定义为环绕。 - Hasturkun
可能 -fwrapv 会禁用我回答中提到的有问题的优化。 - keshlam
看起来你在引用非规范性的附录,虽然我在C99和C11的草案中都找不到这个措辞,你是在引用C89吗?ANSI比较模糊,今天我会认为它指的是C11。 - Shafik Yaghmour
@ShafikYaghmour 我不是100%确定,但我认为这是C89的最后一份草案之一。 - Marian
你应该引用规范文本,我很确定那是C89,因为在C99和C11中,第3节都是“术语和定义”。这个线程有所有公共草案可用,所以你可以从提供的链接中获取C99和C11草案。 - Shafik Yaghmour

3
在你的代码中,所有的i+1产生了signed integer overflow,这是未定义行为,意味着程序的行为可能会不可预测。我们可以从draft C99 standard6.5Expressions5段中看到它是未定义的(重点在于我自己强调):

如果在表达式求值期间发生异常条件(即结果在其类型的表示范围之外或数学上没有定义),则行为是未定义的。

这也是草案C11标准的同一部分。

有一种可能的方法是使用clang的-fsanitize=undefined选项,它是clang santizer套件的一部分,我们会得到以下运行时错误:

runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'

2
我认为你遇到了一个优化器的情况,编译器认为对于整数,x+1>y可以更有效地计算为x>=y...但不幸的是,尽管这通常是正确的,并且对于无符号值始终正确,但在这种特定情况下,x+1回绕成为负数打破了这个假设。
然而,由于有符号整数溢出使我们进入未定义行为的领域(参见@Marian的答案),因此这并不是错误的。只是令人惊讶。
(考虑删除我的答案,但考虑到它说明了如何同时满足两个测试,我认为值得保留。)

这不是编译器的错误。有符号整数溢出是未定义行为。 - Hasturkun
感谢您,已适当澄清。 - keshlam
这里所涉及的具体优化是-fstrict-overflow。在-O2下,gcc启用了-fstrict-overflow优化,它允许假定带符号整数溢出是未定义的。使用-fwrapv会使编译器假定溢出导致环绕而过。有趣的是,在C99规范中,第3.4.3节给出的未定义行为定义示例是“整数溢出时的行为” - Hasturkun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接