出售域名!

2. 断点

看以下程序:

例 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跟踪循环,找出错误原因并改正。


Host by Unixetc