2.7 调试

现在您已经可以编写一个简单的C语言程序了,但是您可能会犯一些简单的错误。程序的错误通常叫做bugs,而发现和修正这些错误的过程叫做调试(debugging)。程序清单2.4给出了一个带有一些错误的程序,看看您能找出多少。

程序清单2.4 nogood.c程序

阅读 ‧ 电子书库

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

2.7.1 语法错误

程序清单2.4包含了几个语法错误。如果您不遵循C语言的规则就会犯语法错误。它类似于英语中的语法错误。例如,考虑下面的句子:Bugs frustrate be can。句子中的英语单词都是正确的,但是没有按照正确的顺序组织句子,而且单词用得也不是非常准确。C的语法错误是指把正确的C符号放在了错误的位置。

那么程序nogood.c中到底出现了什么语法错误呢?首先,它使用圆括号而不是花括号来包围函数体,这是正确的C符号用错了位置的情况。第二,声明应该采用以下形式:

阅读 ‧ 电子书库

或者采用以下形式:

阅读 ‧ 电子书库

第三,示例程序中忽略了必须用一个*/符号来结束注释(当然也可以用新形式//来替代/*)。最后,程序漏掉了结束printf()语句所必需的分号。

如何检测程序的语法错误?首先,在编译前浏览程序的源代码看看是否有明显的错误。其次,可以查看由编译器发现的错误,因为它的工作之一就是检测语法错误。在编译程序时,编译器会报告所找到的任何错误,同时指出每一个错误的性质和位置。

然而,编译器也会发生错误。某位置上一个真正的语法错误可能导致编译器误认为它发现了其他错误。例如,因为示例程序未能正确声明n2和n3,当后面用到这些变量的时候,编译器可能认为它发现了更多的错误。实际上,不用立刻试图改正所有发现的错误,只是修改前一个或前两个,然后重新编译;其余的某些错误就可能会消失。一直这样做,直到程序能够运行为止。编译器的另一个常见毛病是发现的错误位置比真正的错误要滞后一行。例如,编译器要编译下一行时才发现上一行缺少了一个分号。因此,如果编译器指出某个具有分号的行少了一个分号,那么请检查上一行。

2.7.2 语义错误

语义错误就是在意思上的错误。例如,考虑下面的句子:Furry inflation thinks greenly。句子中形容词、名词、动词和副词的位置都很正确,所以语法没有错,但是句子却什么意思也没表达出来。在C中,当您正确遵循了C语言的规则,但是结果不正确的时候,那就是犯了语义错误。示例程序中有这样一个错误:

阅读 ‧ 电子书库

此处,原本是希望n3代表n的三次方,但是代码把它设置成了n的四次方。

这样的语义错误编译器是检测不到的,因为它并没有违反C语言的规则。编译器无法了解您的真正意图,只好留给您自己去找出这类错误。方法之一是比较程序实际得到的结果和您预期的结果。例如,假设您已经修正了示例程序中的语法错误,现在程序应该如程序清单2.5所示。

程序清单2.5 stillbad.c程序

阅读 ‧ 电子书库

输出结果是:

阅读 ‧ 电子书库

如果您懂得立方的话,就会知道结果625是错误的。下一步要跟踪程序是如何得出这个答案的。对于本例,通过观察您可能会发现其中的错误,但您需要采取更为系统的方法。方法之一就是把自己想像成计算机,跟着程序的步骤一步一步地执行。现在让我们试一下这种方法。

程序体一开始是声明3个变量:n、n2和n3。您可以画3个盒子,并用3个变量的名称作为每个盒子的标签(请参见图2.6)来模拟这种情况。下一步,程序把5赋给变量n,您可以在标签为n的盒子上写进5。接着,程序把n和n相乘,然后把所得结果赋值给n2。因此我们看一下标签为n的盒子,发现它的值等于5。我们用5和5相乘得到25,于是把25放进n2盒子里。为了重现下面的C语句(n3=n2*n2;),我们查找到n2=25;用25乘25,得到625,把结果放进n3的盒子里。哦,原来是在进行n2的平方而不是用n去相乘。

阅读 ‧ 电子书库

图2.6 跟踪一个程序

对于上面的例子,这个过程可能比较烦琐一点。但用这种方式一步一步地查看程序的执行情况通常是发现程序中问题所在的最好方法。

2.7.3 程序状态

通过手工方式一步一步地跟踪程序,并记录每个变量,这样就可以监视程序状态。程序状态(program state)是指在程序执行过程中的给定点上所有变量值的集合。它是当前计算状态的一个快照。

我们刚刚讨论了跟踪程序状态的方法之一:自己逐步执行程序。然而,对于迭代10000次的程序,这样的任务您是不可能完成的。不过,您仍然可以跟踪其中的一小部分选代看看程序是不是按照您所期望的方式执行的。然而,经常有这么一种可能,即您是按照您期望的那样去执行步骤,而不是按照您实际所写的代码去执行的,因此要尽量忠于实际的代码。

另一种查找语义错误的方法是,在程序的几个关键点处加入额外的printf()语句以监视所选变量的值。通过观察变量值的变化可以了解程序的执行情况。当程序的工作让您满意之后,就可以把额外的语句去掉,然后重新编译。

检查程序状态的第三种方法是使用调试器。调试器是一种程序,让您能够一步一步地运行另一个程序并检查该程序的变量值。不同的调试器具有不同的易用性和复杂度。较高级的调试器可以显示出正在执行的源代码行。这对于有多条可选执行路径的程序来说是非常方便的,因为可以很容易地知道执行了哪条特殊路径。如果您的编译器带有调试器,那么现在请花点时间去学会怎么用它。例如,试着去调试一下程序清单2.4。