《C Primer Plus(第6版)中文版》一2.7 调试程序

简介:

本节书摘来自异步社区《C Primer Plus(第6版)中文版》一书中的第2章,第2.7节,作者 傅道坤,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.7 调试程序

现在,你可以编写一个简单的C程序,但是可能会犯一些简单的错误。程序的错误通常叫做bug,找出并修正错误的过程叫做调试(debug)。程序清单2.4是一个有错误的程序,看看你能找出几处。

程序清单2.4 nogood.c程序

/* nogood.c -- 有错误的程序 */
include <stdio.h>
int main(void)
(
    int n, int n2, int n3;

    /* 该程序有多处错误
    n = 5;
    n2 = n * n;
    n3 = n2 * n2;
    printf("n = %d, n squared = %d, n cubed = %d\n", n, n2, n3)

    return 0;
)

2.7.1 语法错误

程序清单2.4中有多处语法错误。如果不遵循C语言的规则就会犯语法错误。这类似于英文中的语法错误。例如,看看这个句子:Bugs frustrate be can[6]。该句子中的英文单词都是有效的单词(即,拼写正确),但是并未按照正确的顺序组织句子,而且用词也不妥。C语言的语法错误指的是,把有效的C符号放在错误的地方。

nogood.c程序中有哪些错误?其一,main()函数体使用圆括号来代替花括号。这就是把C符号用错了地方。其二,变量声明应该这样写:

int n, n2, n3;

或者,这样写:

int n;
int n2;
int n3;

其三,main()中的注释末尾漏掉了/(另一种修改方案是,用//替换/)。最后,printf()语句末尾漏掉了分号。

如何发现程序的语法错误?首先,在编译之前,浏览源代码看是否能发现一些明显的错误。接下来,查看编译器是否发现错误,检查程序的语法错误是它的工作之一。在编译程序时,编译器发现错误会报告错误信息,指出每一处错误的性质和具体位置。

尽管如此,编译器也有出错的时候。也许某处隐藏的语法错误会导致编译器误判。例如,由于nogood.c程序未正确声明n2和n3,会导致编译器在使用这些变量时发现更多问题。实际上,有时不用把编译器报告的所有错误逐一修正,仅修正第1条或前几处错误后,错误信息就会少很多。继续这样做,直到编译器不再报错。编译器另一个常见的毛病是,报错的位置比真正的错误位置滞后一行。例如,编译器在编译下一行时才会发现上一行缺少分号。因此,如果编译器报错某行缺少分号,请检查上一行。

2.7.2 语义错误

语义错误是指意思上的错误。例如,考虑这个句子:Scornful derivatives sing greenly(轻蔑的衍生物不熟练地唱歌)。句中的形容词、名词、动词和副词都在正确的位置上,所以语法正确。但是,却让人不知所云。在C语言中,如果遵循了C规则,但是结果不正确,那就是犯了语义错误。程序示例中有这样的错误:

n3 = n2 * n2;

此处,n3原意表示n的3次方,但是代码中的n3被设置成n的4次方(n2 = n * n)。

编译器无法检测语义错误,因为这类错误并未违反C语言的规则。编译器无法了解你的真正意图,所以你只能自己找出这些错误。例如,假设你修正了程序的语法错误,程序应该如程序清单2.5所示:

程序清单2.5 stillbad.c程序

/* stillbad.c -- 修复了语法错误的程序 */
#include <stdio.h>
int main(void)
{
    int n, n2, n3;

    /* 该程序有一个语义错误 */
    n = 5;
    n2 = n * n;
    n3 = n2 * n2;
    printf("n = %d, n squared = %d, n cubed = %d\n", n, n2, n3);

    return 0;
}

该程序的输出如下:

n = 5, n squared = 25, n cubed = 625

如果对简单的立方比较熟悉,就会注意到625不对。下一步是跟踪程序的执行步骤,找出程序如何得出这个答案。对于本例,通过查看代码就会发现其中的错误,但是,还应该学习更系统的方法。方法之一是,把自己想象成计算机,跟着程序的步骤一步一步地执行。下面,我们来试试这种方法。

main()函数体一开始就声明了3个变量:n、n2、n3。你可以画出3个盒子并把变量名写在盒子上来模拟这种情况(见图2.6)。接下来,程序把5赋给变量n。你可以在标签为n的盒子里写上5。接着,程序把n和n相乘,并把乘积赋给n2。因此,查看标签为n的盒子,其值是5,5乘以5得25,于是把25放进标签为n2的盒子里。为了模拟下一条语句(n3 = n2 * n2),查看n2盒子,发现其值是25。25乘以25得625,把625放进标签为n3的盒子。原来如此!程序中计算的是n2的平方,不是用n2乘以n得到n的3次方。


7d13f6401c297b344209cee24e785d94f7202833

图2.6 跟踪程序的执行步骤

对于上面的程序示例,检查程序的过程可能过于繁琐。但是,用这种方法一步一步查看程序的执行情况,通常是发现程序问题所在的良方。

2.7.3 程序状态

通过逐步跟踪程序的执行步骤,并记录每个变量,便可监视程序的状态。程序状态(program state)是在程序的执行过程中,某给定点上所有变量值的集合。它是计算机当前状态的一个快照。

我们刚刚讨论了一种跟踪程序状态的方法:自己模拟计算机逐步执行程序。但是,如果程序中有10000次循环,这种方法恐怕行不通。不过,你可以跟踪一小部分循环,看看程序是否按照预期的方式执行。另外,还要考虑一种情况:你很可能按照自己所想去执行程序,而不是根据实际写出来的代码去执行。因此,要尽量忠实于代码来模拟。

定位语义错误的另一种方法是:在程序中的关键点插入额外的printf()语句,以监视制定变量值的变化。通过查看值的变化可以了解程序的执行情况。对程序的执行满意后,便可删除额外的printf()语句,然后重新编译。

检测程序状态的第3种方法是使用调试器。调试器(debugger)是一种程序,让你一步一步运行另一个程序,并检查该程序变量的值。调试器有不同的使用难度和复杂度。较高级的调试器会显示正在执行的源代码行号。这在检查有多条执行路径的程序时很方便,因为很容易知道正在执行哪条路径。如果你的编译器自带调试器,现在可以花点时间学会怎么使用它。例如,试着调试一下程序清单2.4。

相关文章
|
C语言
《C Primer Plus(第6版)中文版》一导读
C Primer Plus(第6版)中文版 1984年C Primer Plus 第1版刚问世时,使用C语言编程的人并不多。C语言从那时开始流行,许多人在本书的帮助下掌握了C语言。实际上,C Primer Plus 各个版本累计销售量已超过55万册。
2456 0