【问题标题】:strange integer behavior with gcc -O2gcc -O2 的奇怪整数行为
【发布时间】:2012-10-04 14:06:59
【问题描述】:
#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++ 标签。)

【问题讨论】:

  • 不太愿意为可能更有经验的开发人员提供建议,但无论如何它可能对没有经验的开发人员有用。如果我看到仅由优化级别引起的“奇怪”差异,我首先要寻找的是 UB。

标签: c++ c gcc integer compiler-optimization


【解决方案1】:

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

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

【讨论】:

  • 没有什么能阻止编译器定义标准中的 UB,例如非捕获二进制补码算法。所以人们一定想知道,进行所谓的优化有什么好处?没有,据我所知。这是一个白痴的优化,恕我直言。因此,虽然编译器在形式上是在其权利范围内,但至少在这方面它是一个低质量实现
  • pedr0 的回答中有一个例子。有关更多信息,请参见此处:blog.llvm.org/2011/05/…
  • 非常感谢您的链接!请注意,前两段强烈暗示了“X 是做 Y 的一种可能方式,因此 Y 需要 X”这样的逻辑谬误。在那之后我停止了阅读...... :-)
  • @Cheersandhth.-Alf 你能具体说明什么公式暗示了这种谬误吗?我在“有符号整数溢出”部分或整篇文章的前两段中都没有找到任何内容。
  • @DanielFischer:例如,在第一段中, X" 优化为 "true">> 作为 >意味着[因为 X=overflow-is-UB 是实现 Y=optimization-of-x+1>x 的一种方式,因此 Y=optimization-of-x 需要 X=overflow-is-UB +1>x]。这是一个谬误。在第二段中,对逻辑的故意无知,或者如果你愿意的话,欺骗,甚至更糟:>作为一般陈述(它假装是)公然不真实。
【解决方案2】:

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

-fstrict-溢出 允许编译器采用严格的有符号溢出规则,具体取决于正在编译的语言。对于 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 级别启用。

【讨论】:

    猜你喜欢
    • 2015-02-24
    • 2016-09-29
    • 2011-12-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-16
    • 1970-01-01
    • 2014-06-06
    • 1970-01-01
    相关资源
    最近更新 更多