[閒聊] 令人驚訝的未定義行為例子

作者: nh60211as   2022-04-26 11:33:33
原文: https://mohitmv.github.io/blog/Shocking-Undefined-Behaviour-In-Action/
看到一篇 C++ 未定義行為的簡單例子分享一下
簡單的有限迴圈變成無窮迴圈
1. #include <iostream>
2.
3. int main() {
4. char buf[50] = "y";
5. for (int j = 0; j < 9; ++j) {
6. std::cout << (j * 0x20000001) << std::endl;
7. if (buf[0] == 'x') break;
8. }
9. }
編譯器: GCC 11.1.0
編譯選項: -Wall -Wextra -std=c++17 -pedantic-errors
https://wandbox.org/permlink/h1zB3mYSD3pCHiIV
使用以上編譯選項程式印出 9 次數字後就會正常結束
但是最佳化(-O2 或是 -O3)後執行會變成無窮迴圈
這是因為段程式碼有未定義行為(signed integer overflow)
由於編譯器在最佳化時可以假設 signed integer overflow 不會發生
編譯器會先將程式碼轉換為
1. for (int p = 0; p < 9 * 0x20000001; p += 0x20000001) {
2. std::cout << p << std::endl;
3. if (buf[0] == 'x') break;
4. }
接著把" p < 9 * 0x20000001 " 簡化為 true
因為 4,831,838,217 (9*0x20000001) 比 p 可能的最大值 INT_MAX 還大
所以結果永遠為 true
程式碼因此變成
1. for (int p = 0; true; p+=0x20000001) {
2. std::cout << p << std::endl;
3. if (buf[0] == 'x') break;
4. }
而形成無窮迴圈
作者: Lipraxde (Lipraxde)   2022-04-26 12:31:00
原來 loop 優化會這樣做喔!(◎_◎;)
作者: descent (「雄辯是銀,沉默是金」)   2022-04-26 14:55:00
感謝分享
作者: pinefruit (莫使惹塵埃)   2022-04-27 00:54:00
有趣!感謝分享~
作者: milkdragon (謝謝大家!!)   2022-04-28 08:13:00
推~~感謝分享
作者: CaptainH (Cannon)   2022-04-28 10:01:00
一直很不理解這種"優化"道理何在
作者: longlongint (華哥爾)   2022-04-28 10:33:00
用常數先算好 把乘法拔掉而已應該先講拔常數乘法 再講overflow 會比較順?原po的寫法會讓人覺得編譯器在衝三小XD
作者: nh60211as   2022-04-28 10:42:00
我也覺得怪怪的,可是如果直接把p < 9*0x20000001寫在 loop 條件裡編譯器會報 -Woverflow 警告,所以它最佳化的方法滿有它在銃三小的感覺
作者: longlongint (華哥爾)   2022-04-28 19:51:00
真的是衝三小耶
作者: tinlans ( )   2022-04-29 08:29:00
這是 -faggressive-loop-optimizations 在 cunroll 做的事情,其實不用 -O3,用 -O1 編譯就看得見了,還會有警告,有興趣的可以編譯時下 -fdump-tree-all-details 然後看產生出來的 <file>.169t.cunroll 在做什麼,參考它前一步<file>.156t.copyprop3 的輸出做前後比對。
作者: suhorng ( )   2022-05-03 15:50:00
@CaptainH 應該是典型的 induction variable elimination跟其他最佳化互動造成的意外畢竟程式原本沒有 undefined behavior 的話, 那換不換都不應該造成意外的結果. 所以產生意外的互動只能怪程式
作者: wulouise (在線上!=在電腦前)   2022-05-08 20:01:00
有UB就是程式有問題,所以會有什麼結果都不奇怪
作者: OnlyRD (里巷人)   2022-05-15 20:31:00
所以都要 cpplint & cppcheck 過才能上,用cppcheck就抓出來了。
作者: Killercat (殺人貓™)   2022-05-25 10:55:00
不知道這例子 把p宣告為volatile int有沒有幫助
作者: LPH66 (-6.2598534e+18f)   2022-05-25 18:18:00
volatile 管的東西跟這個完全無關, 這並不是存取最佳化而是計算上的最佳化, 加上一些因為 UB 而有的假設出現的再提一次: UB = 編譯器可以方便行事, 假設不會有 UB

Links booklink

Contact Us: admin [ a t ] ucptt.com