[闲聊] 令人惊讶的未定义行为例子

楼主: 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