习题4 使用调试器

我在Ubuntu系统下学习,因此只演示GDB 

GDB是GNU调试器。用来调试程序,我可以通过设置断点和单步运行并返回回溯信息来定位程序出错的位置,从而方便我修正程序。

这是一种调试的方式。在习题19还会学定义宏并使用宏来调试程序和打印调试信息。

跟着视频做

本节习题以视频为中心。、

1 为了演示更好理解,我先在demo文件夹下创建一个源文件ex4.c,其中的crash函数在调用printf函数会出错,因为test是零地址NULL,prinf打印时会访问这个地址,这是操作系统不允许的。现在只需要知道这一点就好。文件内容如下:

笨方法学C语言 学习记录 习题4_GDB

2 假装我现在不知道我的代码会出错,然后和往常一样,我对我的源文件进行编译和运行,如下图:

笨方法学C语言 学习记录 习题4_GDB_02

发现我的源文件顺利通过了编译,但是运行的时候出错了,并且提示段错误,但没有更详细的错误信息,我不知道哪里出错了。(其实可以看出来是在打印I am 72 inches tall之后,那就是在调用crash函数的时候出错,crash函数有问题,但我仍然假装给我的信息暂时不足以让我找到问题所在吧。)

3 这时候,我就要使用GDB来进行调试,找到错误在哪里了。使用GDB调试也是在一个类似终端的程序中运行程序,不过这个程序具有我们进行调试需要的功能。所以,需要先编译源文件生成目标文件,不过要让GDB能够对它进行调试,在编译时,我要加上CFLAGS="-Wall -g"的修饰符。这一点,我在Makefile中已经做了。所以只要使用make命令,就会自动加上这个修饰符。如下图:

笨方法学C语言 学习记录 习题4_调试_03

这就是Makefile的好处,我们不必每次要调试都手动加上修饰符了。

4 然后是在gdb中加载ex4目标文件,如下图:

笨方法学C语言 学习记录 习题4_C语言_04

这时候我的命令提示为(gdb),不是美元符号。

5 然后我就可以使用gdb命令进行调试。我可以在gdb中运行我的程序,使用run命令(run后面还可以加上要传给程序的命令行参数),如下图:

笨方法学C语言 学习记录 习题4_笨方法学C语言_05

可以看到,gdb返回给我错误发生的函数名称和所在的行。

6 我可以使用break命令设置断点(break后面接函数名),让程序运行到断点位置暂停一下,并且使用step运行下一行(若碰到函数调用则进入被调用函数第一行),或者使用next运行下一行(但遇到函数调用不进入被调用函数内部),如下面两张图所示:

笨方法学C语言 学习记录 习题4_GDB_06

笨方法学C语言 学习记录 习题4_GDB_07

我在main函数设置断点(main函数体第一行为第15行),然后run,程序运行到第15行就暂停了,然后我使用step逐行运行至printf函数并进入printf函数(printf函数的第一行),进入print函数后使用next只逐行运行但不进入printf函数调用的函数内部,最后使用step运行printf函数的最后一步从而跳出printf函数,再使用next运行第二个printf函数但不再进入print函数内部,再使用step进入crash函数(crash函数的第一行),然后再逐步运行,直到crash函数内,源文件的第9行,运行出错,发现出错位置。

找到哪里出错以后,我就返回源文件,仔细阅读出错的代码,理清代码逻辑,修正错误,重新编译运行和调试,直到确认编译和运行都没有问题。

整体上来说,现在需要知道的差不多就这么多,当然,书上和视频中还有其他一些命令,比如程序暂停后要继续运行程序则使用continue命令,清除当前行断点clear,列出源文件中接下来的10行代码list和退出gdb命令quit等,意思都很明了,稍微自己试一下就懂了。

7 还有一个命令,bt,转储当前调用栈的回溯信息,不懂得什么意思,可能得学数据结构、进程线程等才能理解。书上和视频提供了一个技巧,编译完成后,直接执行gdb --batch --ex run --ex bt --ex q --args ./ex4,则会运行程序至终止并返回错误信息(如果有的话),如下图:

笨方法学C语言 学习记录 习题4_C语言_08

这样有时可以更快地获得错误信息,而不必慢慢地调试。

其他

以上大约就是使用GDB的步骤了。我先编译和运行程序,然后看有没有错误,有问题再使用GDB来找出问题,然后修正它。不过,也可以编译后直接使用GDB来运行看会不会出错。我现在更喜欢自己先运行一遍。

调试也是一个大主题,要想习得高效且熟练的调试技能,书上和视频中提供的内容显然是不够的,还需要自己阅读其他书籍和进行大量实践锻炼出来。不过,用于本书的学习,这些内容已经足以达到其目的了。