如果程序運行時出現段錯誤,用gdb可以很容易定位到究竟是哪一行引發的段錯誤,例如這個小程序:
調試過程如下:
$ gdb main ... (gdb) r Starting program: /home/akaedu/main 123 Program received signal SIGSEGV, Segmentation fault. 0xb7e1404b in _IO_vfscanf () from /lib/tls/i686/cmov/libc.so.6 (gdb) bt #0 0xb7e1404b in _IO_vfscanf () from /lib/tls/i686/cmov/libc.so.6 #1 0xb7e1dd2b in scanf () from /lib/tls/i686/cmov/libc.so.6 #2 0x0804839f in main () at main.c:6
在gdb中運行,遇到段錯誤會自動停下來,這時可以用命令查看當前執行到哪一行代碼了。gdb顯示段錯誤出現在_IO_vfscanf函數中,用bt命令可以看到這個函數是被我們的scanf函數調用的,所以是scanf這一行代碼引發的段錯誤。仔細觀察程序發現是man前面少了個&。
繼續調試上一節的程序,上一節最後提出修正Bug的方法是在循環中加上判斷條件,如果不是數字就報錯退出,不僅輸入字母可以報錯退出,輸入超長的字元串也會報錯退出。表面上看這個程序無論怎麼運行都不出錯了,但假如我們把while (1)循環去掉,每次執行程序只轉換一個數:
例 10.5. 段錯誤調試實例二
#include <stdio.h>
int main(void)
{
int sum = 0, i = 0;
char input[5];
scanf("%s", input);
for (i = 0; input[i] != '\0'; i++) {
if (input[i] < '0' || input[i] > '9') {
printf("Invalid input!\n");
sum = -1;
break;
}
sum = sum*10 + input[i] - '0';
}
printf("input=%d\n", sum);
return 0;
}然後輸入一個超長的字元串,看看會發生什麼:
$ ./main 1234567890 Invalid input! input=-1
看起來正常。再來一次,這次輸個更長的:
$ ./main 1234567890abcdef Invalid input! input=-1 Segmentation fault
又出段錯誤了。我們按同樣的方法用gdb調試看看:
$ gdb main
...
(gdb) r
Starting program: /home/akaedu/main
1234567890abcdef
Invalid input!
input=-1
Program received signal SIGSEGV, Segmentation fault.
0x0804848e in main () at main.c:19
19 }
(gdb) l
14 }
15 sum = sum*10 + input[i] - '0';
16 }
17 printf("input=%d\n", sum);
18 return 0;
19 }gdb指出,段錯誤發生在第19行。可是這一行什麼都沒有啊,只有表示main函數結束的}括號。這可以算是一條規律,如果某個函數的局部變數發生訪問越界,有可能並不立即產生段錯誤,而是在函數返回時產生段錯誤。
想要寫出Bug-free的程序是非常不容易的,即使scanf讀入字元串這麼一個簡單的函數調用都會隱藏着各種各樣的錯誤,有些錯誤現象是我們暫時沒法解釋的:為什麼變數i的存儲單元緊跟在input數組後面?為什麼同樣是訪問越界,有時出段錯誤有時不出段錯誤?為什麼訪問越界的段錯誤在函數返回時才出現?還有最基本的問題,為什麼scanf輸入整型變數就必須要加&,否則就出段錯誤,而輸入字元串就不要加&?這些問題在後續章節中都會解釋清楚。其實現在講scanf這個函數為時過早,讀者還不具備充足的基礎知識。但還是有必要講的,學完這一階段之後讀者應該能寫出有用的程序了,然而一個只有輸出而沒有輸入的程序算不上是有用的程序,另一方面也讓讀者認識到,學C語言不可能不去瞭解底層計算機體繫結構和操作系統的原理,不瞭解底層原理連一個scanf函數都沒辦法用好,更沒有辦法保證寫出正確的程序。