簡體   English   中英

為什么GCC -O2和-O3優化打破了這個程序?

[英]Why does GCC -O2 and -O3 optimization break this program?

我編寫了這個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 <2)但這是剛做一個隨機測試,看看會發生什么,我在這里發布,以找出究竟發生了什么:)

循環for (i = 3; i > 2; i++)並且它沒有break語句或其他退出條件。

最終i將達到INT_MAX ,然后i++將導致整數溢出,從而導致未定義的行為。

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

當程序保證觸發未定義的行為時,程序的整個行為是不確定的。

gcc以積極優化觸發UB的路徑而聞名。 您可以檢查匯編代碼,看看您的情況究竟發生了什么。 也許-O2和更高的情況下,除去循環結束條件檢查,但-O1離開它在那里和“依賴” INT_MAX + 1產生INT_MIN

我發現以下代碼在沒有優化的情況下編譯的匯編程序結果與-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(正如我期望在PC上)

使用-Os代碼結果:

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

我認為第二個結果是錯誤! 我認為編譯器應該編譯對應於代碼的東西:

的printf( “%d \\ n”, - 2147483648);

正如您所注意到的那樣,未定義有符號整數溢出。 編譯器決定推斷您的程序,假設您足夠聰明,永遠不會導致未定義的行為。 因此,它可以得出結論,因為i被初始化為大於2的數字並且僅增加,所以它永遠不會低於或等於2,這意味着i > 2永遠不會是假的。 這反過來意味着循環永遠不會終止,並且可以優化為無限循環。

正如你所說,它是未定義的行為,所以你不能依賴任何特定的行為。

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

  • 編譯器或多或少地直接轉換為機器代碼,它可以在溢出發生時執行它想做的任何事情(通常是轉到最負值)並且仍然包括測試(例如,如果值,則會失敗)翻身)
  • 編譯器觀察到索引變量從3開始並且總是增加,因此循環條件總是成立,因此它會發出一個永遠不會測試循環條件的無限循環

我不知道你在嘗試什么,但如果你想處理整數溢出,只需在源代碼中包含limits.h並在for循環中寫下這一行。

if (i >= INT_MAX) break;

這將使您能夠檢查您的變量是否大於它是否適合整數。

for循環for(i=3; i>2; i++)並且在此循環內部i沒有被修改,也沒有break或任何其他方式退出循環。 您依靠整數溢出來導致退出條件發生,但編譯器不會考慮這一點。

相反,編譯器看到i從3開始,並且i只是遞增,所以i>2總是如此。 因此,在這種情況下根本不存在i ,因為這必須是無限循環。

如果將i更改為unsigned int並將循環出口的條件設置為匹配,則不再出現此“優化”。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM