第 16章 常见错误和程序调试
16.1 常见错误分析
16.2 程序调试
要真正学好 C、用好 C并不容易,“灵活”固然是好事,但也使人难以掌握,尤其是初学者往往出了错还不知怎么回事。 C编译程序对语法的检查不如其他高级语言那样严格 (这是为了给程序人员留下“灵活”的余地 )。因此,往往要由程序设计者自己设法保证程序的正确性。调试一个 C程序要比调试一个 PASCAL或 FORTRAN程序更困难一些。需要不断积累经验,提高程序设计和调试程序的水平。
C语言有些语法规定和其他高级语言不同,学习过其他高级语言的读者往往按照使用其他高级语言的习惯来写 C程序,这也是出错的一个原因。
16.1 常见错误分析
下面将初学者在学习和使用 C语言 (不包括 C++)时容易犯的错误列举出来,以起提醒的作用。这些内容在以前各章中大多已谈到,为便于查阅,在本章中集中列举,供初学者参考,以此为鉴。
(1) 忘记定义变量。如:
main( )
{ x=3;
y=6;
printf("%d\n ",x+y);

C要求对程序中用到的每一个变量都必须定义其类型,上面程序中没有对 x,y进行定义。应在函数体的开头加 int x,y;这是学过 BASIC和
FORTRAN语言的读者写 C程序时常见的一个错误。
在 BASIC语言中,可以不必先定义变量类型就可直接使用。在 FORTRAN中,未经定义类型的变量按隐含的 I-N规则决定其类型,而 C语言则要求对用到的每一个变量都要在本函数中定义 (除非已定义为外部变量 )。
(2) 输入输出的数据的类型与所用格式说明符不一致。
例如,若 a已定义为整型,b已定义为实型。
a=3;b=4,5;
printf("%f %d\n",a,b);
编译时不给出出错信息,但运行结果将与原意不符,输出为
0,000000 16402
它们并不是按照赋值的规则进行转换 (如把 4,5转换成 4),而是将数据在存储单元中的形式按格式符的要求组织输出 (如 b占 4个字节,只把最后两个字节中的数据按 %d,作为整数输出 )。
(3) 未注意 int型数据的数值范围。
一般微型计算机上使用的 C编译系统,对一个整型数据分配两个字节。因此一个整数的范围为
-215~ 215-1,即 -32768~ 32767。常见这样的程序段:
int num;
num=89101;
printf("%d",num);
得到的却是 23565,原因是 89101已超过 32767。两个字节容纳不下 89101,则将高位截去。见图 16.1。
即将超过低 16位的数截去。即将 89101减去 216(即
16位二进制所形成的模 )。 89101-65536=23565。
图 16.1
有时还会出现负数。例如
num=196607;
输出得 -1。因为 196607的二进制形式为
00 00 00 00 00 00 00 1011 11 11 11 11 11 11 11
去掉高位 10,低 16位的值是 -1(-1的补码是:
1111111111111111)。
对于超过整个范围的数,要用 long型,即改为
long int num;
num=89101;
printf("%ld",num);
请注意,如果只定义 num为 long型,而在输出时仍用,%d” 说明符,仍会出现以上错误。
(4) 输入变量时忘记使用地址符。如:
scanf("%d%d",a,b);
这是许多初学者刚学习 C语言时一个常见的疏忽,
或者说是习惯性的错误,因为在其他语言中在输入时只需写出变量名即可,而 C语言要求指明
“向哪个地址标识的单元送值”。应写成
scanf("%d%d",&a,&b);
(5) 输入时数据的组织与要求不符。
用 scanf函数输入数据,应注意如何组织输入数据。假如有以下 scanf函数:
scanf("%d%d",&a,&b);
有人按下面的方法输入数据:
3,4
这是错的。数据间应该用空格来分隔。读者可以用
printf("%d%d",a,b);
来验证一下。应该用以下方法输入:
34
如果 scanf函数为
scanf("%d,%d",&a,&b);
对 scanf函数中格式字符串中除了格式说明符外,
对其他字符必须按原样输入。因此,应按以下方法输入:
3,4
此时如果用,34”反而错了。还应注意,不能企图用
scanf("input a & b,%d,%d",&a,&b);
想在屏幕上显示一行信息:
input a & b:
然后在其后输入 a和 b的值,这是不行的。这是由于有的读者以为 scanf具有 BASIC语言中的 INPUT
语句的功能 (先输出一个字符串,再输入变量的值 )。
如果想在屏幕上得到所需的提示信息,可以另加一个 printf函数语句,printf("input a & b,");
scanf("%d,%d",&a,&b);
(6) 误把,=”作为“等于”比较符。
在许多高级语言中,用,=”符号作为关系运算符
“等于”。例如,在 BASIC或 PASCAL程序中都可以写
if(a=b) then…
但在 C语言中,,=”是赋值运算符,,==”才是关系运算符“等于”。如果写成
if(a=b) printf("a equal to b");
C编译系统将 (a=b)作为赋值表达式处理,将 b的值赋给 a,然后判断 a的值是否零,若为非零,则作为“真” ;若为零作为假。如果 a的值为 3,b的值为 4,
a≠b,按原意不应输出,ae q u a lt ob”。而现在先将 b的值赋给 a,a也为 4,赋值表达式的值为 4。
if语句中的表达式值为真 (非零 ),因此输出,ae q
u a lt o b”。
这种错误在编译时是检查不出来的,但运行结果往往是错的。而且由于习惯的影响,程序设计者自己往往也不易发觉。
(7) 语句后面漏分号。
C语言规定语句末尾必须有分号。分号是 C语句不可缺少的一部分。这也是和其他语言不同的。有的初学者往往忘记写这一分号。如:
a=3
b=4
编译时,编译程序在,a=3”后面未发现分号,就把下一行,b=4”也作为上一行的语句的一部分,
这就出现语法错误。有时编译时指出某行有错,
但在该行上并未发现错误,应该检查上一行是否漏了分号。
如果用复合语句,有的学过 PASCAL语言的读者往往漏写最后一个语句的分号,如:
{ t=a;
a=b;
b=t

在 PASCAL中分号是两个语句间的分隔符而不是语句的一部分,而在 C中,没有分号的就不是语句。
(8) 在不该加分号的地方加了分号。
例如:
if(a> b);
printf("a is larger than b\n");
本意为当 a> b时输出,a is larger than b”的信息。
但由于在 if(a> b)后加了分号,因此 if语句到此结束。
即当 (a> b)为真时,执行一个空语句。本来想 a≤b
时不输出上述信息,但现在 printf函数语句并不从属于 if语句,而是与 if语句平行的语句。不论
a> b还是 a≤b,都输出,a is larger than b”。
又如:
for(i=0;i< 10;i++);
{ scanf("%d",&x);
printf("%d\n",x*x);

本意为先后输入 10个数,每输入一个数后输出它的平方值。由于在 for( )后加了一个分号,使循环体变成了空语句。只能输入一个整数并输出它的平方值。
总之,在 if,for,while语句中,不要画蛇添足多加分号。
(9) 对应该有花括弧的复合语句,忘记加花括弧。
如:
sum=0;
i=1;
while(i< =100)
sum=sum+i;
i++;
本意是实现 1+2+…+100,即 ∑i。但上面的语句只是重复了 sum+1的操作,而且循环永不终止。因为
i的值始终没有改变。错误在于没有写成复合语句形式。因此 while语句的范围到其后第一个分号为止。语句,i++;”不属于循环体范围之内。应改
100
i=0

while(i< =100)
{ sum=sum+i;
i++;

(10) 括弧不配对。
当一个语句中使用多层括弧时常出现这类错误,纯属粗心所致。如,while((c=getchar( )!
='#')
putchar(c);
少了一个右括弧。
(11) 在用标识符时,忘记了大写字母和小写字母的区别。
例如:
main( )
{ int a,b,c;
a=2;b=3;
C=A+B;
printf("%d+%d=%",A,B,C);

编译时出错。编译程序把 a和 A认作是两个不同的变量名处理,同样 b和 B,c和 C都分别代表两个不同的变量。
(12) 引用数组元素时误用了圆括弧。
如:
main( )
{ int i,a(10);
for(i=0;i< 10;i++)
scanf("%d",&a(i));

C语言中对数组的定义或引用数组元素时必须用方括弧。
(13) 在定义数组时,将定义的“元素个数”误认为是“可使用的最大下标值”。
main( )
{ int a[10]={ 1,2,3,4,5,6,7,8,9,
10} ;
int i;
for(i=1;i< =10;i++)
printf("%d",a[i]);

想输出 a[1]到 a[10]。但一些初学者常犯的错误。 C
语言规定定义时用 a[10],表示 a数组有 10个元素,
而不是可以用的最大下标值为 10。数组只包括 a[0]
到 a[9]10个元素,因此用 a[10]就超出 a数组的范围了。
(14) 对二维或多维数组的定义和引用的方法不对。
main( )
{ int a[5,4];

printf("%d",a[1+2,2+2]);


对二维数组和多维数组在定义和引用时必须将每一维的数据分别用方括弧括起来。上面 a[5,4]应改为 a[5][4],a[1+2,2+2]应改为 a[1+2][2+2]。根据 C的语法规则,在一个方括弧中的是一个维的下标表达式,a[1+2,2+2]中方括弧中的,1+2,
2+2”
是一个逗号表达式,它的值是第二个数值表达式的值,即 2+2的值为 4。所以 a[1+2,2+2]相当于
a[4]。而 a[4]是 a数组的第 4行的首地址。因此执行
printf函数输出的结果并不是 a[3][4]的值,而是 a
数组第 4行的首地址。
(15) 误以为数组名代表数组中全部元素。
例如:
main( )
{ int a[4]={ 1,3,5,7} ;
printf("%d%d%d%d\n",a);

企图用数组名代表全部元素。在 C语言中,数组名代表数组首地址,不能通过数组名输出 4个整数。
(16) 混淆字符数组与字符指针的区别。
main( )
{ char str[4];
str="Computer and c";
printf("%s\n",str);

编译出错。 str是数组名,代表数组首地址。在编译时对 str数组分配了一段内存单元,因此在程序运行期间 str是一个常量,不能再被赋值。因此,
str=“Computer and c”是错误的。如果把,char
str[4];”改成,char str;”,则程序正确。此时 str
是指向字符数据的指针变量,str=“Computer and
c”是合法的,它将字符串的首地址赋给指针变量
str,然后在 printf函数语句中输出字符串
,Computer and c”。
因此应当弄清楚字符数组与字符指针变量用法的区别。
(17) 在引用指针变量之前没有对它赋予确定的值。
main( )
{ char*p;
scanf("%s",p);


没有给指针变量 p赋值就引用它,编译时给出警告信息。应当改为
char p,c[20];
p=c;
scanf("%s",p);
即先根据需要定义一个大小合适的字符数组 c,然后将 c数组的首地址赋给指针变量 p,此时 p有确定的值,指向数组 c。再执行 scanf函数就没有问题了,
把从键盘输入的字符串存放到字符数组 c中。
(18) switch语句的各分支中漏写 break语句。
例如:
switch(score)
{ case 5,printf("V ery good! ");
case 4,printf("Good! ");
case 3,printf("Pass! ");
case 2,printf("Fail! ");
defult,printf("data error! ");

上述 switch语句的作用是希望根据 score(成绩 )打印出评语。但当 score的值为 5时,输出为
V ery Good! Good! Pass! Fail! data error!
原因是漏写了 break语句。 case只起标号的作用,
而不起判断作用,因此在执行完第一个 printf函数语句后接着执行第 2,3,4,5个 printf函数语句。
应改为
switch(score)
{ case 5,printf("V erygood! ");break;
case 4,printf("Good! "); break;
case 3,printf("Pass! "); break;
case 2,print("Fail! "); break;
defult,print("data error! ");

(19) 混淆字符和字符串的表示形式。
char sex;
sex="M";

sex是字符变量,只能存放一个字符。而字符常量的形式是用单引号括起来的,应改为
sex='M';
,M”是用双引号括起来的字符串,它包括两个字符:‘ M’和‘ \0’,无法存放到字符变量 sex中。
(20) 使用自加 (++)和自减 (--)运算符时出的错误。
例如:
main( )
{ int p,a[5]={ 1,3,5,7,9} ;
p=a;
printf("%d",*p++);

不少人认为,*p++”的作用是先使 p加 1,即指向第
1个元素 a[1]处,然后输出第一个元素 a[1]的值 3。
其实应该是先执行 p++,而 p++的作用是先用 p的原值,用完后使 p加 1。 p的原值指向数组 a的第 0个元素 a[0],
因此 *p就是第 0个元素 a[0]的值 1。结论是先输出
a[0]的值,然后再使 p加 1。如果是 *(++p),则先使
p指向 a[1],然后输出 a[1]的值。
(21) 有人习惯用传统的方式对函数形参进行声明,
但却把对函数的形参和函数中的局部变量混在一起定义。如:
max(x,y)
int x,y,z ;
{z =x> y? x,y;
return(z );

应改为
max(x,y)
int x,y;
{ int z ;
z =x> y? x,y;
return(z );

(22) 所调用的函数在调用语句之后才定义,而又在调用前未加说明。
main( )
{ float x,y,z ;
x=3,5;y=-7,6;
z =max(x,y);
printf("%f\n",z );

float max(float x,float y)
{ return(z =x> y? x,y);

这个程序乍看起来没有什么问题,但在编译时有出错信息。原因是 max函数是实型的,而且在
main函数之后才定义,也就是 max函数的定义位置在 main函数中的调用 max函数之后。改错的方法可以用以下二者之一:
① 在 main函数中增加一个对 max函数的声明,
即函数的原型:
main( )
{ float max(float,float);/*声明将要用到的
max函数为实型 */
float x,y,z ;
x=3,5;y=-7,6;
z =max(x,y);
printf("%f\n",z );

② 将 max函数的定义位置调到 main函数之前。即:
float max(float x,float y)
{ return(z =x> y? x,y);}
main()
{ float x,y,z ;
x=3,5;y=-7,6;
z =max(x,y);
printf("%f\n",z );

这样,编译时不会出错,程序运行结果是正确的。
(23) 误认为形参值的改变会影响实参的值。
main( )
{ inta,b;
a=3;b=4;
swap(a,b);
printf("%d,%d\n",a,b);

swap(int x,int y)
{ int t;
t=x;x=y;y=t;

原意是通过调用 swap函数使 a和 b的值对换,然后在 main函数中输出已对换了值的 a和 b。但是这样的程序是达不到目的的,因为 x和 y的值的变化是不传送回实参 a和 b的,main函数中的 a和 b的值并未改变。
如果想从函数得到一个以上的变化了的值,应该用指针变量。用指针变量作函数参数,使指针变量所指向的变量的值发生变化。此时变量的值改变了,主调函数中可以利用这些已改变的值。如:
main( )
{ int a,b,*p1,*p2;
a=3;b=4;
p1=&a;p2=&b;
swap(p1,p2);
printf("%d,%d\n",a,b); /a和 b的值已对换
/

swap(int *pt1,int *pt2)
{ int t;
t=*pt1;*pt1=*pt2;*pt2=t;

(24) 函数的实参和形参类型不一致。
main( )
{ int a=3,b=4;
c=fun(a,b);


fun(float x,float y)



实参 a,b为整型,形参 x,y为实型。 a和 b的值传递给 x和 y时,x和 y的值并非 3和 4。 C要求实参与形参的类型一致。如果在 main函数中对 fun作原型声明:
fun (float,float);
程序可以正常运行,此时,按不同类型间的赋值的规则处理,在虚实结合后 x=3.0,y=4.0。也可以将 fun函数的位置调到 main函数之前,也可获正确结果。
(25) 不同类型的指针混用。
main( )
{ int i=3,*p1;
float a=1,5,*p2;
p1=&i; p2=&a;
p2=p1;
printf("%d,%d\n",*p1,*p2);

企图使 p2也指向 i,但 p2是指向实型变量的指针,
不能指向整型变量。指向不同类型的指针间的赋值必须进行强制类型转换。如:
p2=(float*)p1;
作用是先将 p1的值转换成指向实型的指针,然后再赋给 p2。
这种情况在 C程序中是常见的。例如,用 malloc函数开辟内存单元,函数返回的是指向被分配内存空间的 void *类型的指针。而人们希望开辟的是存放一个结构体变量值的存储单元,要求得到指向该结构体变量的指针,可以进行如下的类型转换。
struct student
{ int num;
char name[20];
float score;
} ;
struct student student1,*p;

p=(struct student *)malloc(LEN);
p是指向 struct student结构体类型数据的指针,将
malloc函数返回的 void *类型指针转换成指向
struct student类型变量的指针。
(26) 没有注意函数参数的求值顺序。例如有以下语句:
i=3;
printf("%d,%d,%d\n",i,++i,++i); 许多人认为输出必然是
3,4,5
实际不尽然。在 Turbo C和其他一些 C系统中输出是
5,5,4
因为这些系统是采取自右至左的顺序求函数参数的值。先求出最右面一个参数 (++i)的值为 4,再求出第 2个参数 (++i)的值为 5,最后求出最左面的参数 (i)
的值 5。
C标准没有具体规定函数参数求值的顺序是自左而右还是自右而左。但每个 C编译程序都有自己的顺序,在有些情况下,从左到右求解和从右到左求解的结果是相同的。例如
fun1(a+b,b+c,c+a);
fun1是一个函数名。 3个实参表达式 a+b,b+c,c+a。
在一般情况下,自左至右地求这 3个表达式的值和自右至左地求它们的值是一样的,但在前面举的例子是不相同的。因此,建议最好不用会引起二义性的用法。如果在上例中,希望输出,3,4,5”时,
可以改用
i=3; j=i+1; k=j+1;
printf("%d,%d,%d\n",i,j,k);
(27) 混淆数组名与指针变量的区别。
main( )
{ int i,a[5];
for(i=0;i< 5;i++)
scanf("%d",a++);


企图通过 a的改变使指针下移,每次指向欲输入数据的数组元素。它的错误在于不了解数组名代表数组首地址,它的值是不能改变的,用 a++是错误的,应当用指针变量来指向各数组元素。即:
int i,a[5],*p;
p=a;
for(i=0;i< 5;i++)
scanf("%d",p++);
或 int a[5],*p;
for(p=a;p< a+5;p++)
scanf("%d",p);
(28) 混淆结构体类型与结构体变量的区别,对一个结构体类型赋值。
struct worker
{ long int num;
char name[20];
char sex;
int age;
} ;
worker,num=187045;
strcpy(worker,name,"ZhangFun");
worker,sex='M';
worker,age=18;
这是错误的,只能对变量赋值而不能对类型赋值。
上面只定义了 struct worker类型而未定义变量。
应改为
struct worker
{ long int num;
char name[20];
char sex;
int age;
} ;
struct worker worker-1;
worker-1,num=187045;
strcpy(worker-1,name,"Zhang Fun");
worker-1,sex='M';
worker-1,age=18;
今定义了结构体变量 worker-1,并对其中的各成员赋值。
(29) 使用文件时忘记打开,或打开方式与使用情况不匹配。
例如,对文件的读写,用只读方式打开,却企图向该文件输出数据,例如:
if((fp=fopen("test","r"))==NULL)
{ printf("cannot open this file\n");
exit(0);

ch=fgetc(fp);
while(ch! ='#')
{ ch=ch+4;
fputc(ch,fp);
ch=fget(fp);

对以,r”方式 (只读方式 )打开的文件,进行既读又写的操作,显然是不行的。
此外,有的程序常忘记关闭文件,虽然系统会自动关闭所用文件,但可能会丢失数据。因此必须在用完文件后关闭它。
以上只是列举了一些初学者常出现的错误,这些错误大多是对于 C语法不熟悉之故。对 C语言使用多了,比较熟练了,犯这些错误自然就会减少了。
在深入使用 C语言后,还会出现其他一些更深入、
更隐蔽的错误。
程序出错有三种情况:
① 语法错误。指违背了 C语法的规定,对这类错误,编译程序一般能给出“出错信息”,并且告诉你在哪一行出错。只要细心,是可以很快发现并排除的。
② 逻辑错误。程序并无违背语法规则,但程序执行结果与原意不符。这是由于程序设计人员设计的算法有错或编写程序有错,通知给系统的指令与解题的原意不相同,即出现了逻辑上的混乱。
例如:前面第 9条错误:
sum=0;i=1;
while(i< =100)
sum=sum+i;
i++;
语法并无错误。但 while语句通知给系统的信息是当 i≤100时,执行,sum=sum+i;”。 C系统无法辨别程序中这个语句是否符合作者的原意,而只能忠实地执行这一指令。这种错误比语法错误更难检查。要求程序员有较丰富的经验。
③ 运行错误。程序既无语法错误,也无逻辑错误,
但在运行时出现错误甚至停止运行。例如:
int a,b,c;
scanf("%d %d",&a,&b);
c=b/a;
printf("c=%d\n",c);
输入 a和 b的值,输出 b/a的值,程序没有错。 但是如果输入 a的值为 0,就会出现错误。 因此程序应能适应不同的数据,或者说能经受各种数据的
“考验”,具有“健壮性”。
写完一个程序只能说完成任务的一半 (甚至不到一半 )。调试程序往往比写程序更难,更需要精力、
时间和经验。常常有这样的情况:程序花一天就写完了,而调试程序二三天也未能完。有时一个小小的程序会出错五六处,而发现和排除一个错误,有时竟需要半天,甚至更多。希望读者通过实践掌握调试程序的方法和技术。
16.2 程 序 调 试
所谓程序调试是指对程序的查错和排错。
调试程序一般应经过以下几个步骤:
(1) 先进行人工检查,即静态检查。在写好一个程序以后,不要匆匆忙忙上机,而应对纸面上的程序进行人工检查。这一步是十分重要的,它能发现程序设计人员由于疏忽而造成的多数错误。而这一步骤往往容易被人忽视。有人总希望把一切推给计算机系统去做,但这样就会多占用机器时间。而且,作为一个程序人员应当养成严谨的科学作风,每一步都要严格把关,不把问题留给后面的工序。 为了更有效地进行人工检查,所编的程序应注意力求做到以下几点:①应当采用结构化程序方法编程,以增加可读性 ;② 尽可能多加注释,以帮助理解每段程序的作用 ;③ 在编写复杂的程序时,不要将全部语句都写在 main函数中,而要多利用函数,
用一个函数来实现一个单独的功能。这样既易于阅读也便于调试,各函数之间除用参数传递数据这一渠道以外,数据间尽量少出现耦合关系,便于分别检查和处理。
(2) 在人工 (静态 )检查无误后,才可以上机调试。通过上机发现错误称动态检查。在编译时给出语法错误的信息 (包括哪一行有错以及错误类型 ),可以根据提示的信息具体找出程序中出错之处并改正之。
应当注意的是:有时提示的出错行并不是真正出错的行,如果在提示出错的行上找不到错误的话应当到上一行再找。另外,有时提示出错的类型并非绝对准确,由于出错的情况繁多而且各种错误互有关联,因此要善于分析,找出真正的错误,
而不要只从字面意义上死抠出错信息,钻牛角尖。
如果系统提示的出错信息多,应当从上到下逐一改正。有时显示出一大片出错信息往往使人感到问题严重,无从下手。其实可能只有一二个错误。
例如,对所用的变量未定义,编译时就会对所有含该变量的语句发出出错信息。只要加上一个变量定义,所有错误都消除了。
(3) 在改正语法错误 (包括“错误” (error)和“警告” (warning))后,程序经过连接 (link)就得到可执行的目标程序。运行程序,输入程序所需数据,
就可得到运行结果。应当对运行结果作分析,看它是否符合要求。有的初学者看到输出运行结果就认为没问题了,不作认真分析,这是危险的。
有时,数据比较复杂,难以立即判断结果是否正确。
可以事先考虑好一批“试验数据”,输入这些数据可以得出容易判断正确与否的结果。例如解方程
ax2+bx+c=0,输入 a,b,c的值分别为 1,-2,1时,
根 x的值是 1。这是容易判断的,若根不等于 1,程序显然有错。
但是,用“试验数据”时,程序运行结果正确,还不能保证程序完全正确。因为有可能输入另一组数据时运行结果不对。例如,用 x=-b± b2-4ac2a公式求根 x的值,当 a≠0和 b2-4ac> 0时,能得出正确结果,当 a=0或 b2-4ac< 0时,就得不到正确结果 (假设程序中未对 a=0作防御处理以及未作复数处理 )。
因此应当把程序可能遇到的多种方案都一一试到。
例如,if语句有两个分支,有可能在流程经过其中一个分支时结果正确,而经过另一个分支时结果不对。必须考虑周全。
事实上,当程序复杂时很难把所有的可能方案全部都试到,选择典型的情况作试验即可。
(4) 运行结果不对,大多属于逻辑错误。对这类错误往往需要仔细检查和分析才能发现。可以采用以下办法:
① 将程序与流程图 (或伪代码 )仔细对照,如果流程图是正确的话,程序写错了,是很容易发现的。
例如,复合语句忘记写花括弧,只要一对照流程图就能很快发现。
② 如果实在找不到错误,可以采取“分段检查”
的方法。在程序不同位置设几个 printf函数语句,
输出有关变量的值,逐段往下检查。直到找到在某一段中数据不对为止。这时就已经把错误局限在这一段中了。不断缩小“查错区”,就可能发现错误所在。
③ 也可以用第 9章介绍过的“条件编译”命令进行程序调试 (在程序调试阶段,若干 printf函数语句要进行编译并执行。当调试完毕,这些语句不要再编译了,也不再被执行了 )。这种方法可以不必一一删去 printf函数语句,以提高效率。
④ 如果在程序中没有发现问题,就要检查流程图有无错误,即算法有无问题,如有则改正之,接着修改程序。
⑤ 有的系统还提供 debug(调试 )工具,跟踪流程并给出相应信息,使用更为方便,请查阅有关手册。
总之,程序调试是一项细致深入的工作,需要下功夫、
动脑子、善于累积经验。在程序调试过程中往往反映出一个人的水平、经验和科学态度。希望读者能给以足够的重视。上机调试程序的目的决不是为了“验证程序的正确性”,而是“掌握调试的方法和技术”。