9
#include <stdio.h>
#include <limits.h>

void sanity_check(int x)
{
    if (x < 0)
    {
        x = -x;
    }
    if (x == INT_MIN)
    {
        printf("%d == %d\n", x, INT_MIN);
    }
    else
    {
        printf("%d != %d\n", x, INT_MIN);
    }
    if (x < 0)
    {
        printf("negative number: %d\n", x);
    }
    else
    {
        printf("positive number: %d\n", x);
    }
}

int main(void)
{
    sanity_check(42);
    sanity_check(-97);
    sanity_check(INT_MIN);
    return 0;
}

当我用 编译上述程序时gcc wtf.c,我得到了预期的输出:

42 != -2147483648
positive number: 42
97 != -2147483648
positive number: 97
-2147483648 == -2147483648
negative number: -2147483648

但是,当我使用 编译程序时gcc -O2 wtf.c,会得到不同的输出:

42 != -2147483648
positive number: 42
97 != -2147483648
positive number: 97
-2147483648 != -2147483648
positive number: -2147483648

注意最后两行。这到底是怎么回事?gcc 4.6.3 优化是否过于急切?

(我还用 g++ 4.6.3 对此进行了测试,我观察到了同样的奇怪行为,因此使用了 C++ 标签。)

4

2 回答 2

15

当您执行 -(INT_MIN) 时,您正在调用未定义的行为,因为该结果不适合 int。

gcc -O2 注意到 x 永远不会是负数并在此后进行优化。它不在乎你是否溢出了这个值,因为它是未定义的,它可以随心所欲地对待它。

于 2012-10-04T14:11:49.457 回答
12

我认为这可以帮助你,来自这里:这里

-fstrict-overflow 允许编译器采用严格的有符号溢出规则,具体取决于正在编译的语言。对于 C(和 C++),这意味着在对有符号数进行算术运算时溢出是未定义的,这意味着编译器可能会假设它不会发生。这允许各种优化。例如,编译器会假设像 i + 10 > i 这样的表达式对于有符号 i 始终为真。此假设仅在有符号溢出未定义时才有效,因为如果在使用二进制补码算术时 i + 10 溢出,则表达式为假。当此选项生效时,任何确定对有符号数的操作是否会溢出的尝试都必须仔细编写,以免实际涉及溢出。此选项还允许编译器假设严格的指针语义:给定一个指向对象的指针,如果向该指针添加偏移量不会产生指向同一对象的指针,则添加未定义。这允许编译器得出结论,对于指针 p 和无符号整数 u,p + u > p 始终为真。这个假设仅是有效的,因为指针环绕是未定义的,因为如果 p + u 使用二进制补码算术溢出,则表达式为假。

另请参见 -fwrapv 选项。使用 -fwrapv 意味着整数有符号溢出是完全定义的:它会换行。使用 -fwrapv 时,对于整数,-fstrict-overflow 和 -fno-strict-overflow 没有区别。使用 -fwrapv 允许某些类型的溢出。例如,如果编译器在对常量进行算术运算时发生溢出,溢出的值仍然可以与 -fwrapv 一起使用,但不能用于其他情况。

-fstrict-overflow 选项在 -O2、-O3、-Os 级别启用。

于 2012-10-04T14:11:37.863 回答