内容简介:一次偶然碰到一个诡异的bug,现象是同一份C++代码使用示例代码如下:将这段代码分别使用
一次偶然碰到一个诡异的bug,现象是同一份C++代码使用 GCC4.4.x 版本在开启优化前和优化后的结果不一样,优化后的代码逻辑不正确。
示例代码如下:
//main.cpp
#include <stdio.h>
enum Type {
ERR_A = -1,
ERR_B = 0,
ERR_C = 1,
};
void func(Type tt) {
switch(tt){
case ERR_A:
printf("case ERR_A, tt = %d\n", tt);
break;
case ERR_B:
printf("case ERR_B, tt = %d\n", tt);
break;
case ERR_C:
printf("case ERR_C, tt = %d\n", tt);
break;
default:
printf("case default, tt = %d\n", tt);
break;
}
}
int main(){
Type tt = (Type)4;
func(tt); //预期输出case default
tt = (Type)1;
func(tt); //预期输出case ERR_C
tt = (Type)-4;
func(tt); //预期输出case default
return 0;
}
将这段代码分别使用 g++ -O0
和 g++ -O1
编译,结果让人诧异,在tt=4的时候,switch却跳到了1的分支。
$ g++ -O0 -g -o main main.cpp $ ./main case default, tt = 4 case ERR_C, tt = 1 case default, tt = -4 $ g++ -O1 -g -o main main.cpp $ ./main case ERR_C, tt = 1 case ERR_C, tt = 1 case default, tt = -4
排查过程
考虑到是有enum存在,可能是枚举超出定义范围而被GCC优化掉了,在网上找到一篇 帖子 ,大意是讲enum是以int类型存储的,同时32bit在cpu中有更快的处理效率。 通过单步调试和watch命令也会发现tt的值一直是4,且没有被更改,因此可以排除enum undefined这种情况。
于是只能去看汇编代码了,事实证明这才是最有效的方式,比自己瞎猜要节省时间。
可以通过调试时使用 disas
命令查看汇编代码,也可以使用 objdump
直接看二进制的汇编代码。
对比下debug(上)和release(下)两种情况下的汇编代码。
# 未开启优化
(gdb) b 26
Breakpoint 1 at 0x400620: file main.cpp, line 26.
(gdb) r
...
(gdb) n
27 func(tt);
(gdb) s
func (tt=4) at main.cpp:10
10 switch(tt){
(gdb) disas /m
Dump of assembler code for function func(Type):
9 void func(Type tt){
0x00000000004005a4 <+0>: push %rbp
0x00000000004005a5 <+1>: mov %rsp,%rbp
0x00000000004005a8 <+4>: sub $0x10,%rsp
0x00000000004005ac <+8>: mov %edi,-0x4(%rbp)
10 switch(tt){
=> 0x00000000004005af <+11>: mov -0x4(%rbp),%eax
0x00000000004005b2 <+14>: test %eax,%eax
0x00000000004005b4 <+16>: je 0x4005d6 <func(Type)+50>
0x00000000004005b6 <+18>: cmp $0x1,%eax
0x00000000004005b9 <+21>: je 0x4005ec <func(Type)+72>
0x00000000004005bb <+23>: cmp $0xffffffffffffffff,%eax
0x00000000004005be <+26>: jne 0x400602 <func(Type)+94>
11 case ERR_A:
12 printf("case ERR_A, tt = %d\n", tt);
0x00000000004005c0 <+28>: mov -0x4(%rbp),%eax
...
14 case ERR_B:
15 printf("case ERR_B, tt = %d\n", tt);
0x00000000004005d6 <+50>: mov -0x4(%rbp),%eax
...
17 case ERR_C:
18 printf("case ERR_C, tt = %d\n", tt);
0x00000000004005ec <+72>: mov -0x4(%rbp),%eax
...
20 default:
21 printf("case default, tt = %d\n", tt);
0x0000000000400602 <+94>: mov -0x4(%rbp),%eax
# 开启O1优化选项
(gdb) b 26
Breakpoint 1 at 0x400611: file main.cpp, line 26.
(gdb) r
...
(gdb) n
case ERR_C, tt = 1
29 func(tt);
(gdb) s
func (tt=ERR_C) at main.cpp:9
9 void func(Type tt){
(gdb) disas /m
Dump of assembler code for function func(Type):
9 void func(Type tt){
=> 0x00000000004005a4 <+0>: sub $0x8,%rsp
10 switch(tt){
0x00000000004005a8 <+4>: test %edi,%edi
0x00000000004005aa <+6>: je 0x4005cb <func(Type)+39>
0x00000000004005ac <+8>: test %edi,%edi
0x00000000004005ae <+10>: jg 0x4005e1 <func(Type)+61>
0x00000000004005b0 <+12>: cmp $0xffffffffffffffff,%edi
0x00000000004005b3 <+15>: jne 0x4005f7 <func(Type)+83>
11 case ERR_A:
12 printf("case ERR_A, tt = %d\n", tt);
0x00000000004005b5 <+17>: mov $0xffffffff,%esi
...
14 case ERR_B:
15 printf("case ERR_B, tt = %d\n", tt);
0x00000000004005cb <+39>: mov $0x0,%esi
...
17 case ERR_C:
18 printf("case ERR_C, tt = %d\n", tt);
0x00000000004005e1 <+61>: mov $0x1,%esi
...
20 default:
21 printf("case default, tt = %d\n", tt);
0x00000000004005f7 <+83>: mov %edi,%esi
...
可以看到在 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 4.4.X版本优化switch-enum的BUG》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Learning JavaScript
Shelley Powers / Oreilly & Associates Inc / 2006-10-17 / $29.99
As web browsers have become more capable and standards compliant, JavaScript has grown in prominence. JavaScript lets designers add sparkle and life to web pages, while more complex JavaScript has led......一起来看看 《Learning JavaScript》 这本书的介绍吧!
MD5 加密
MD5 加密工具
UNIX 时间戳转换
UNIX 时间戳转换