(x<y)==(-x>-y)这个表达式是真还是假?

3
问题来自于cs:app3e 2.82。 我了解到当x = INT_MIN时,-x也是-INT_MIN,但是
#include <stdio.h>
#include <limits.h>

int main() {
    int x = INT_MIN, y = -3;
    printf("%d\n", (x < y) == (-x > -y));
    return 0;
}

在我的机器上(Linux版本6.2.0-34-generic(buildd@bos03-amd64-059)(x86_64-linux-gnu-gcc-11(Ubuntu 11.4.0-1ubuntu1~22.04)11.4.0,GNU ld(GNU Binutils for Ubuntu)2.38)),这会输出1。 为什么会发生这种情况?
我使用gcc -o进行编译。 我还使用gcc -O0进行编译。

10
-INT_MIN 是未定义行为。请参考:https://stackoverflow.com/questions/71081310/why-does-int-min-int-min-and-how-the-negative-sign-operate-in-bit-level#comment125653666_71081600 - undefined
1
这个问题对我来说不太清楚。_"我了解到当x = INT_MIN时,-x也是-INT_MIN"_并不完全正确,因为可能存在其他人所描述的未定义行为。但是,除了1以外,你对于(x < y) == (-x > -y)的结果还期望什么呢?也许我需要喝咖啡.... - undefined
1
我了解到当x = INT_MIN时,-x也是-INT_MIN的结论是错误的。 - undefined
2
你可以尝试使用-fwrapv来获得你期望的行为,或者使用-fsanitize=undefined来获得解释。 - undefined
相关:当x是有符号整数且x=1时,找到一个值y,使得(x < y) == (-x > -y)为假?(没有明确定义的整数环绕,例如来自-fwrapv)。半相关:GCC在x86-64与AArch64上对-x>-y的优化不同:C程序'(-x > -y)'在macOS和Linux上的输出不同-原因和如何修复 - undefined
3个回答

14
在你的平台上,就像许多其他平台一样(使用二进制补码而没有陷阱表示,这是大多数现代平台的情况),对于取反INT_MIN是未定义行为。编译器可以假设未定义行为不会发生,并且可以按照任何他们喜欢的方式行事,包括荒谬的方式。因此,gcc的优化器(无论优化设置如何,仍然以最低级别运行)在分析(x<y)==(-x>-y)时可以排除INT_MIN的情况,认为它是重言式,并在不执行任何运行时比较的情况下替换为1。实际上,它不执行任何运行时比较,只是直接加载1进行打印。

2
实际的常量INT_MIN和INT_MAX可能会有些令人困惑。如果我们查看C标准(C17 5.2.4.2.1),它说:INT_MIN -32767和INT_MAX +32767,这意味着这些是必须支持的最小值,对于一个16位的int。为了简单起见,下面的所有示例都将假设为16位的int(当然,32/64位的二进制补码系统使用2^31 - 1和-2^32,因此为2147483647/-2147483648)。
为什么选择这些值是出于历史原因。除了行业标准的二进制补码外,C还支持两种奇特的符号格式:反码和补码。在后两种格式中,我们有一个负零和/或陷阱表示,给出了一个可能的值范围为-32767到32767。
但是绝大多数计算机都使用二进制补码,因此在16位系统上,INT_MIN变成了-32768。这在标准中是可以接受的,因为它只是说明INT_MIN必须至少为-32767。而在二进制补码中,INT_MAX仍然是32767。
因此,在二进制补码系统中,我们不能执行int x = INT_MIN; x = -x;,因为INT_MAX是32767,无法容纳32768的值。这将导致整数溢出,这是未定义行为 - 编译器可以生成任何代码,包括奇怪和荒谬的代码。
在即将发布的C23标准中,对于奇异的符号格式的支持将最终从C中删除。然后,INT_MIN在标准中很可能也会变成-32768。

0
这会输出1。为什么会这样呢?
正如其他答案已经解释过的那样,在C语言中,有符号溢出是未定义行为,因此编译器可能会假设如果x是有符号的,那么-x不会溢出。
由于你的问题特别涉及到gcc:GCC支持命令行选项-fwrapv,这样有符号溢出就会像无符号溢出一样进行包装。通过在命令行选项中添加-fwrapv(或者-fno-strict-overflow),你的程序将打印0(false)。
这个选项可能会禁用一些优化并启用其他优化。有趣的是看看它对稍微不同的程序的影响。
#include <stdio.h>
#include <limits.h>

int x = INT_MIN, y = -3;

int main()
{
    printf ("%d\n", (x < y) == (-x > -y));
    return 0;
}

在编译时不再知道xy的值的情况下:

  • 当使用-O2 -fno-wrapv进行编译时,编译器会将(x < y) == (-x > -y)替换为字面值1,而不考虑xy的值。这是因为如果没有发生算术溢出,表达式的结果为真,编译器可能假设如果发生溢出,它不会溢出。这与有符号溢出的未定义行为一致。

  • 当使用-O2 -fwrapv进行编译时,编译器无法在编译时评估(x < y) == (-x > -y),它会生成代码在运行时评估表达式。对于提供的特定xy的值,结果为假。

你也可以看看诊断选项,比如-Wstrict-overflow-Woverflow。当然还有-Wall

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