起因
一次偶然碰到一个诡异的bug,现象是同一份C++代码使用GCC4.4.x版本在开启优化前和优化后的结果不一样,优化后的代码逻辑不正确。
示例代码如下:
1 | //main.cpp |
将这段代码分别使用 g++ -O0
和 g++ -O1
编译,结果让人诧异,在tt=4的时候,switch却跳到了1的分支。
1 | $ g++ -O0 -g -o main main.cpp |
排查过程
考虑到是有enum存在,可能是枚举超出定义范围而被GCC优化掉了,在网上找到一篇帖子,大意是讲enum是以int类型存储的,同时32bit在cpu中有更快的处理效率。 通过单步调试和watch命令也会发现tt的值一直是4,且没有被更改,因此可以排除enum undefined这种情况。
于是只能去看汇编代码了,事实证明这才是最有效的方式,比自己瞎猜要节省时间。
可以通过调试时使用disas
命令查看汇编代码,也可以使用objdump
直接看二进制的汇编代码。
对比下debug(上)和release(下)两种情况下的汇编代码。
1 | # 未开启优化 |
1 | # 开启O1优化选项 |
可以看到在O0
时,汇编逻辑为:等于0时跳到case B,等于1跳到了case C,不等于-1跳到default, 等于-1到case A。
而在O1
时,汇编逻辑为: 等于0时跳到case B,大于0直接跳到了case C,不等于-1跳到default, 等于-1到case A。
出错的原因就在于开启编译优化后,GCC对大于零的情况默认其为case C(1),这里推测是由于test
是使用位运算,而cmp
是使用加减运算,使用test提高了运算效率。 但是这种改变代码逻辑,让逻辑出错的优化显然是让人难以接受的。
官方解释
如此诡异的问题虽然找到了原因,但内心还是无法接受这是GCC犯的错误。
经过谷歌一番,找到了这篇帖子, 果然有人也踩到了同样的坑。
这是一个GCC4.4版本被反馈过的bug,尽管这个优化很不合理,但依然被作为一个“feature”被保留下来…
在高版本GCC中,使用-std=c++03 -fstrict-enum
选项可以开启这个”特性”,该特性假设编程者会保证enum的取值在其定义范围内。
最后,解决这个问题的方法有两种,在switch之前做一次enum的范围检查,或者使用更高版本GCC。
其他
最后的最后,附一个查询资料时看到的关于GCC对switch做的优化…
祝大家周末愉快:)
参考
- what is the size of an enum type data in C++? - https://stackoverflow.com/questions/8115550/what-is-the-size-of-an-enum-type-data-in-c
- Guard code after switch on enum is never reached - https://stackoverflow.com/questions/8679534/guard-code-after-switch-on-enum-is-never-reached/8679627
- Bug 41425 - switch with enums doesn’t work - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=41425
- Options Controlling C++ Dialect - https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Dialect-Options.html#index-fstrict-enums
- From Switch Statement Down to Machine Code - http://lazarenko.me/switch/