1. 程式人生 > 實用技巧 >記錄一個有趣的bug

記錄一個有趣的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,必須反彙編。