看以下程序:
例 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 = 0
undisplay
命令可以取消跟蹤顯示,變數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
跟蹤循環,找出錯誤原因並改正。