[英]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++
將導致整數溢出,從而導致未定義的行為。
可能Sum
或Result
也會在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
永遠不會是假的。 這反過來意味着循環永遠不會終止,並且可以優化為無限循環。
正如你所說,它是未定義的行為,所以你不能依賴任何特定的行為。
您最有可能看到的兩件事是:
我不知道你在嘗試什么,但如果你想處理整數溢出,只需在源代碼中包含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.