为什么GCC -O2和-O3优化打破了这个程序?

时间:2015-09-28 13:40:09

标签: c gcc optimization infinite-loop integer-overflow

我已经编写了这个C代码,用于查找所有整数的总和,它们等于数字的阶乘之和。在没有任何GCC优化标志的情况下完成工作需要一分钟左右,使用-O1将时间缩短约15-20秒但是当我尝试使用-O2,-O3或-Os时,它会陷入无限循环

int main()
{
  int i, j, factorials[10];
  int Result=0;

  for(i=0; i<10; i++)
  {
    factorials[i]=1;
    for(j=i; j>0; j--)
    {
      factorials[i] *= j;
    }
  }

  for(i=3; i>2; i++) //This is the loop the program gets stuck on
  {
    int Sum=0, number=i;

    while(number)
    {
      Sum += factorials[number % 10];
      number /= 10;
    }

    if(Sum == i)
      Result += Sum;
  }

  printf("%d\n", Result);  
  return 0;
}

我发现for(i=3; i>2; i++)是导致问题的原因。显然i永远不会少于2?

这是否与整数溢出行为未定义有关?如果是这样,在这些情况下,还有关于程序究竟发生了什么的更多信息?

编辑:我想我应该提到,我知道其他写for循环的方法,以便它不会使用溢出(我希望INT_MAX + 1等于INT_MIN,是&lt; 2)但是这只是作为一个随机测试来完成,看看会发生什么,我在这里发布它以找出究竟发生了什么:)

6 个答案:

答案 0 :(得分:4)

循环为for (i = 3; i > 2; i++)且没有break语句或其他退出条件。

最终i将达到INT_MAX,然后i++将导致整数溢出,从而导致未定义的行为。

可能SumResult也会在i之前溢出。

当程序保证触发未定义的行为时,程序的整个行为是未定义的。

众所周知,gcc积极优化触发UB的路径。您可以检查汇编代码,看看您的情况究竟发生了什么。也许-O2和更高的案例删除了循环结束条件检查,但-O1将其留在那里并“依赖”INT_MAX + 1导致INT_MIN

答案 1 :(得分:1)

我发现以下代码在没有优化和-Os优化的情况下编译的汇编程序结果之间存在差异。

#include <stdio.h>

int main(){
    int i;

    for(i=3;i>2;i++);

    printf("%d\n",i);

    return 0;
}

没有优化代码结果:

000000000040052d <main>:
  40052d:   55                      push   %rbp
  40052e:   48 89 e5                mov    %rsp,%rbp
  400531:   48 83 ec 10             sub    $0x10,%rsp
  400535:   c7 45 fc 03 00 00 00    movl   $0x3,-0x4(%rbp)
  40053c:   c7 45 fc 03 00 00 00    movl   $0x3,-0x4(%rbp)
  400543:   eb 04                   jmp    400549 <main+0x1c>
  400545:   83 45 fc 01             addl   $0x1,-0x4(%rbp)
  400549:   83 7d fc 02             cmpl   $0x2,-0x4(%rbp)
  40054d:   7f f6                   jg     400545 <main+0x18>
  40054f:   8b 45 fc                mov    -0x4(%rbp),%eax
  400552:   89 c6                   mov    %eax,%esi
  400554:   bf f4 05 40 00          mov    $0x4005f4,%edi
  400559:   b8 00 00 00 00          mov    $0x0,%eax
  40055e:   e8 ad fe ff ff          callq  400410 <printf@plt>
  400563:   b8 00 00 00 00          mov    $0x0,%eax
  400568:   c9                      leaveq 
  400569:   c3                      retq  

,输出为:-2147483648(正如我在电脑上预期的那样)

使用-Os代码结果:

0000000000400400 <main>:
  400400:   eb fe                   jmp    400400 <main>

我认为第二个结果是错误!!!我认为编译器应该编译对应于代码的东西:

的printf(&#34;%d \ n&#34;, - 2147483648);

答案 2 :(得分:0)

正如您所注意到的那样,未定义有符号整数溢出。编译器决定推断您的程序,假设您足够聪明,永远不会导致未定义的行为。因此可以得出结论,因为i被初始化为大于2的数字并且仅增加,所以它永远不会低于或等于2,这意味着i > 2永远不会为假。这反过来意味着循环永远不会终止,并且可以优化为无限循环。

答案 3 :(得分:0)

正如你所说,它是未定义的行为,所以你不能依赖任何特定的行为。

您最有可能看到的两件事是:

  • 编译器或多或少直接转换为机器代码,它可以在溢出发生时执行任何想要执行的操作(通常是转到最负值)并且仍然包含测试(例如,将失败)如果值滚动)
  • 编译器观察到索引变量从3开始并且总是增加,因此循环条件总是成立,因此它会发出一个永远不会测试循环条件的无限循环

答案 4 :(得分:0)

我不知道你在尝试什么,但是如果你想处理整数溢出,只需在你的源代码中包含limits.h并在你的for循环中写下这一行。

if (i >= INT_MAX) break;

这将使您能够检查您的变量是否大于它是否适合整数。

答案 5 :(得分:0)

for循环是for(i=3; i>2; i++),并且此循环内部i未被修改,也没有break或任何其他方式退出循环。您依靠整数溢出来导致退出条件发生,但编译器不会考虑这一点。

相反,编译器看到i从3开始,而i只是递增,因此i>2始终为真。因此,在此上下文中根本不需要存在i,因为这必须是无限循环。

如果您将i更改为unsigned int并将循环退出的条件设置为匹配,则此“优化”将不再出现。