记录一个有趣的bug
用gcc 8.2.0不同的优化参数编译下面这段代码,运行结果是完全不一样的:
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <unistd.h> 4 5 int a = 0; 6 int b = 1; 7 8 void doA(){ 9 while (a==b) { 10 } 11 } 12 13 int main(){ 14 b = a; 15 16 pthread_t tid; 17 pthread_create(&tid, NULL, (void*)doA, NULL); 18 19 usleep(1000); 20 b = 5; 21 pthread_join(tid, 0); 22 return 0; 23 }
-O0: 程序成功执行完退出.
-O1、-O2、-O3: 程序在pthread_join()这里死等。
为什么呢?
对比反汇编的结果:
、
%rsp 是堆栈指针寄存器,通常会指向栈顶位置,堆栈的 pop 和push 操作就是通过改变 %rsp 的值即移动堆栈指针的位置来实现的。
%rbp 是栈帧指针,用于标识当前栈帧的起始位置
%edx和%eax:如果使用的是64位通用寄存器的低32位,则寄存器以 ”e“ 开头,比如 %eax,%ebx (参考:https://zhuanlan.zhihu.com/p/27339191)
push %rbp:子函数将父函数的栈帧起始地址压栈 mov %rsp,%rbp: 将 %rbp 指向子函数栈帧的起始地址 pop %rbp:退出前将父函数的栈帧起始地址赋给%rbp
nop 空语句,占用一个时钟周期,可以不管。
0x0(%rip),rip寄存器存放当前指令的地址值,这个字段的含义是该地址值加上偏移量0,水平有限,这个地方我猜是读取a和b的值到寄存器,但这个rip如何找到a/b我没搞懂,留个坑。
重点关注:
O0 判断到a==b时,会跳回doA+0x5的位置(je 5),重新从内存上获取a和b的值;
O1 则是跳回doA+0xc的位置,再次执行判断,并不会更新a和b的值。
要命的是,这段代码,while循环体内加一句printf,就会影响编译器的优化,从而使Bug消失,所以加了printf反而无法定位出这个bug,必须反汇编。

浙公网安备 33010602011771号