看以下程序:
例 10.2. 斷點調試實例
#include <stdio.h>
int main(void)
{
int sum = 0, i = 0;
char input[5];
while (1) {
scanf("%s", input);
for (i = 0; input[i] != '\0'; i++)
sum = sum*10 + input[i] - '0';
printf("input=%d\n", sum);
}
return 0;
}這個程序的作用是:首先從鍵盤讀入一串數字存到字元數組input中,然後轉換成整型存到sum中,然後打印出來,一直這樣循環下去。scanf("%s", input);這個調用的功能是等待用戶輸入一個字元串並回車,scanf把其中第一段非空白(非空格、Tab、換行)的字元串保存到input數組中,並自動在末尾添加'\0'。接下來的循環從左到右掃瞄字元串並把每個數字累加到結果中,例如輸入是"2345",則循環累加的過程是(((0*10+2)*10+3)*10+4)*10+5=2345。注意字元型的'2'要減去'0'的ASCII碼才能轉換成整數值2。下面編譯運行程序看看有什麼問題:
$ gcc main.c -g -o main $ ./main 123 input=123 234 input=123234 (Ctrl-C退出程序) $
又是這種現象,第一次是對的,第二次就不對。可是這個程序我們並沒有忘了賦初值,不僅sum賦了初值,連不必賦初值的i都賦了初值。讀者先試試只看代碼能不能看出錯誤原因。下面來調試:
$ gdb main ... (gdb) start Breakpoint 1 at 0x80483b5: file main.c, line 5. Starting program: /home/akaedu/main main () at main.c:5 5 int sum = 0, i = 0;
有了上一次的經驗,sum被列為重點懷疑對象,我們可以用display命令使得每次停下來的時候都顯示當前sum的值,然後繼續往下走:
(gdb) display sum
1: sum = -1208103488
(gdb) n
9 scanf("%s", input);
1: sum = 0
(gdb)
123
10 for (i = 0; input[i] != '\0'; i++)
1: sum = 0undisplay命令可以取消跟蹤顯示,變數sum的編號是1,可以用undisplay 1命令取消它的跟蹤顯示。這個循環應該沒有問題,因為上面第一次輸入時打印的結果是正確的。如果不想一步一步走這個循環,可以用break命令(簡寫為b)在第9行設一個斷點(Breakpoint):
(gdb) l
5 int sum = 0, i;
6 char input[5];
7
8 while (1) {
9 scanf("%s", input);
10 for (i = 0; input[i] != '\0'; i++)
11 sum = sum*10 + input[i] - '0';
12 printf("input=%d\n", sum);
13 }
14 return 0;
(gdb) b 9
Breakpoint 2 at 0x80483bc: file main.c, line 9.break命令的參數也可以是函數名,表示在某個函數開頭設斷點。現在用continue命令(簡寫為c)連續運行而非單步運行,程序到達斷點會自動停下來,這樣就可以停在下一次循環的開頭:
(gdb) c
Continuing.
input=123
Breakpoint 2, main () at main.c:9
9 scanf("%s", input);
1: sum = 123然後輸入新的字元串準備轉換:
(gdb) n 234 10 for (i = 0; input[i] != '\0'; i++) 1: sum = 123
問題暴露出來了,新的轉換應該再次從0開始累加,而sum現在已經是123了,原因在於新的循環沒有把sum歸零。可見斷點有助于快速跳過沒有問題的代碼,然後在有問題的代碼上慢慢走慢慢分析,“斷點加單步”是使用調試器的基本方法。至于應該在哪裡設置斷點,怎麼知道哪些代碼可以跳過而哪些代碼要慢慢走,也要通過對錯誤現象的分析和假設來確定,以前我們用printf打印中間結果時也要分析應該在哪裡插入printf,打印哪些中間結果,調試的基本思路是一樣的。一次調試可以設置多個斷點,用info命令可以查看已經設置的斷點:
(gdb) b 12 Breakpoint 3 at 0x8048411: file main.c, line 12. (gdb) i breakpoints Num Type Disp Enb Address What 2 breakpoint keep y 0x080483c3 in main at main.c:9 breakpoint already hit 1 time 3 breakpoint keep y 0x08048411 in main at main.c:12
每個斷點都有一個編號,可以用編號指定刪除某個斷點:
(gdb) delete breakpoints 2 (gdb) i breakpoints Num Type Disp Enb Address What 3 breakpoint keep y 0x08048411 in main at main.c:12
有時候一個斷點暫時不用可以禁用掉而不必刪除,這樣以後想用的時候可以直接啟用,而不必重新從代碼裡找應該在哪一行設斷點:
(gdb) disable breakpoints 3 (gdb) i breakpoints Num Type Disp Enb Address What 3 breakpoint keep n 0x08048411 in main at main.c:12 (gdb) enable 3 (gdb) i breakpoints Num Type Disp Enb Address What 3 breakpoint keep y 0x08048411 in main at main.c:12 (gdb) delete breakpoints Delete all breakpoints? (y or n) y (gdb) i breakpoints No breakpoints or watchpoints.
gdb的斷點功能非常靈活,還可以設置斷點在滿足某個條件時才激活,例如我們仍然在循環開頭設置斷點,但是僅當sum不等於0時才中斷,然後用run命令(簡寫為r)重新從程序開頭連續運行:
(gdb) break 9 if sum != 0
Breakpoint 5 at 0x80483c3: file main.c, line 9.
(gdb) i breakpoints
Num Type Disp Enb Address What
5 breakpoint keep y 0x080483c3 in main at main.c:9
stop only if sum != 0
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/akaedu/main
123
input=123
Breakpoint 5, main () at main.c:9
9 scanf("%s", input);
1: sum = 123結果是第一次執行scanf之前沒有中斷,第二次卻中斷了。總結一下本節用到的gdb命令:
表 10.2. gdb基本命令2
| 命令 | 描述 |
|---|---|
| break(或b) 行號 | 在某一行設置斷點 |
| break 函數名 | 在某個函數開頭設置斷點 |
| break ... if ... | 設置條件斷點 |
| continue(或c) | 從當前位置開始連續運行程序 |
| delete breakpoints 斷點號 | 刪除斷點 |
| display 變數名 | 跟蹤查看某個變數,每次停下來都顯示它的值 |
| disable breakpoints 斷點號 | 禁用斷點 |
| enable 斷點號 | 啟用斷點 |
| info(或i) breakpoints | 查看當前設置了哪些斷點 |
| run(或r) | 從頭開始連續運行程序 |
| undisplay 跟蹤顯示號 | 取消跟蹤顯示 |
1、看下面的程序:
#include <stdio.h>
int main(void)
{
int i;
char str[6] = "hello";
char reverse_str[6] = "";
printf("%s\n", str);
for (i = 0; i < 5; i++)
reverse_str[5-i] = str[i];
printf("%s\n", reverse_str);
return 0;
}首先用字元串"hello"初始化一個字元數組str(算上'\0'共6個字元)。然後用空字元串""初始化一個同樣長的字元數組reverse_str,相當於所有元素用'\0'初始化。然後打印str,把str倒序存入reverse_str,再打印reverse_str。然而結果並不正確:
$ ./main hello
我們本來希望reverse_str打印出來是olleh,結果什麼都沒有。重點懷疑對象肯定是循環,那麼簡單驗算一下,i=0時,reverse_str[5]=str[0],也就是'h',i=1時,reverse_str[4]=str[1],也就是'e',依此類推,i=0,1,2,3,4,共5次循環,正好把h,e,l,l,o五個字母給倒過來了,哪裡不對了?用gdb跟蹤循環,找出錯誤原因並改正。