第10章
常见错误和程序调试
Commonerrorandprogramdebug
本章概要
Summaryofthechapter
程序调试是程序设计过程中一个必不可少的环节,调试过程中的错误处理则是保证程序正确性的必
要手段。本章将常见的一些错误列举出来,以方便进行程序调试。
C语言是一种介于高级语言与低级语言之间的中级语言,允许直接访问物理地址,能
进行位操作,可以直接对硬件进行操作。程序员使用C语言编写程序会感到限制少、灵活
性较大、功能强,可以编写出能解决复杂问题的、运行效率高、占用内存少的高质量程序,
不仅用来编写系统软件,也用来编写应用软件,所以得到广泛应用。正因为如此,C语言
受到愈来愈广泛的重视,从初学者到高级软件人员,都在学习C和使用C语言。
但是要真正学好、用好C并不容易。因为C语言允许编程人员有较大的自由度,从
而放宽了语法检查,C编译程序对语法的检查不如其他高级语言那样严格,因此,往往要
由程序设计者自己设法保证程序的正确性。这就使人感到难以掌握,尤其是初学者,容易
出错,出了错还不知什么原因、如何处理。另外,C语言有些语法规定和其他高级语言不
同,学习过其他高级语言的读者往往按照使用其他高级语言的习惯来写C程序,这也是出
错的一个原因。
调试一个C程序要比调试一个其它高级语言的程序更困难一些。需要不断积累经验,
提高程序设计和调试程序的水平。
10.1常见错误分析(Commonerroranaly)
程序出错有三种情况:
①语法错误。由于违背了C语言的语法规定而引起的。如双引号或括号不全、do-while
语句缺少while、使用关键字作变量名等,对这类错误,编译程序一般都能检测出来,给出
“出错信息”。并且告诉你在哪一行出错。只要细心,是可以很快发现并排除的。
②逻辑错误。由于程序的结构或算法错误引起的。程序并没有语法错误,程序运行过
程中也没有发生错误,只是最后的运行结果并不是希望的结果。例如,有下面的程序段:
main()
{intsum=0,i=1;
while(i<=100)
·204·第10章常见错误和程序调试
sum=sum+i;
i++;
printf("%dn",sum);
}
语法并无错误。但程序运行时陷入“死循环”,因为while循环语句中,起判断条件的
表达式“i<=100”始终为“真”。C系统无法辨别程序中这个语句是否符合设计者的原意,
而只能忠实地执行这一指令。这种错误比语法错误更难检查。要求程序员有较丰富的经验。
③运行错误。程序既无语法错误,也无逻辑错误,但在运行时出现错误甚至停止运行。
例如:
main()
{inta,b,c;
scanf("%d%d",&a,&b);
c=b/a;
printf("%dn",c);
}
程序没有语法错误,但是程序运行过程中,如果输入数据时a的值为0,就会产生:
Divisionbyzero的错误,即在除法运算中“0”作了除数。
C程序错误检测的首要工具是编译程序,它能把语法上的错误找到并分离出来以信息
的形式显示,显示的信息分:错误(error)和警告(warning)。错误(error)将影响程序执
行,而警告(warning)一般不影响程序的执行,但会影响结果的正确性。
下面将初学者在学习和使用C语言时容易犯的错误列举出来,这些内容在前面各章中
大多己谈到,为便于查阅,在本节集中列举,供初学者参考。
1.数据定义类错误(Errorofdatadefinitionclass)
(1)忘记定义变量。如有:
main()
{x=3;
y=6;
z=x+y;
printf("%dn",z);
}
编译时将出现error信息:
Undefinedsymbol'xxx'
原因是C要求对程序中用到的每一个变量都必须先定义后使用,上面程序中没有对x、
y、z进行定义。应在函数体的开头加上:
intx,y,z;
另外还要注意,C语言中标识符的大小写是不同的,如果定义与使用时大小写不一致
也会出现这样的错误。如有
inta=2,b=3,c;
c=a+B;
第10章常见错误和程序调试·205·
系统会认为“B”没有定义。这是学过BASIC语言的读者常犯的错误,因为BASIC
语言中标识符不分大小写。也可能出现下面情况:
Floatf;
这也是不合法的,C语言的关键字不允许大写。
(2)定义变量的位置错误。如:
main()
{inta,b;
scanf("%d%d",&a,&b);
intc;
c=a+b;
printf("%d",c);
}
编译时将出现error信息:
Expressionsyntax
原因是将变量定义“intc;”放在执行语句后面,系统将其作为一个表达式语句处理,
而不再作为变量的定义。
(3)定义整型变量时没有考虑其数值范围。如有:
inta;
a=45678;
printf("%d",a);
编译时并无错误,但输出的值不是45678,而是-19858,原因是int数的范围为-215~215-1,
即-32768~32767,45678已超出了int型的数值范围。为此可以采用long型,即改为
longinta;
a=45678;
printf("%ld",a);
请注意,如果只定义a为long型,而在输出时仍用“%d”说明符,仍会出现以上错误。
(4)混淆字符与字符串的区别。如有:
charc="a";
编译时将出现warning信息:
Non-portablepointerassignment
原因是定义的字符变量c只能存放一个字符,但赋给它的值"a"是字符串,它包含两个
字符'a'和'0'。C语言对字符串按字符数组处理,是一个指针量,所以系统认为程序将一个
指针量赋给了一个非指针量。改正时,要么将c定义为字符数组或指针变量,要么按以下
方式给字符变量赋值。
①直接赋以字符常量,如:charc='a';
②赋以“转义字符”,如:charc='0';
③赋以一个字符的ASCII码,如:charc=97;
(5)曾经定义并给变量赋值,但程序中没有使用该变量。如有:
main()
·206·第10章常见错误和程序调试
{inta=1,b=2,c=3;
printf("%d,%d",a,b);
}
编译时将出现warning信息:
'c'isassignedavaluewhichisneverud
(6)宏定义或文件包含时漏掉了“#”号。如有:
defintPI3.14159
或
include"stdio.h"
编译时将出现error信息:
Declarationsyntaxerror
(7)混淆结构体类型与结构体变量的区别。如有:
structstudent
{longintnum;
charname[20];
charx;
};
=123;
strcpy(,"zhangfang");
='M';
编译时将出现error信息:
Undefinedsymbolstudent
Illegalstructureoperation
原因是混淆了结构体类型与结构体变量的区别,student是定义的结构体类型而不是结
构体变量,直接对结构体类型student进行操作是非法的。应当在结构体类型定义的同时或
之后,定义一个属于该类型的变量,如:
structstudentstu;
然后对结构体变量stu进行相应的操作。
2.格式类错误(Errorofformatclass)
(1)语句后面遗漏分号。如有:
printf("Goodmorning?n")
编译时的错误信息为:
Statementmissing;
原因是分号是C程序中语句的必要组成部分,每个语句必须以分号结束。这也是与其
它高级语言不同的地方。如果是复合语句,有些初学者往往遗漏最后一个语句的分号。如:
{t=a;a=b;b=t}
(2)在不该加分号的地方加了分号,如有:
for(i=0;i<10;i++);
scanf("%d",a[i]);
第10章常见错误和程序调试·207·
程序并无错误,但程序的执行结果与原意不符。程序的本意是用for循环输入10个整
数给一维数组,但在for后面加上分号后,就单独构成了一个语句,成为一个空循环,实
现不了设计思想。这种错误属于逻辑错误。
请读者注意,这是一个常见的错误,尤其是在if、for、while语句中要特别注意。
(3)括弧不配对。
包括三种情况:花括号、中括号、圆括号不配对。这类错误,纯属粗心所致。
①花括号不配对,编译将会出现error信息:
Compoundstatementmissing}
当一个语句中使用多层花括弧时常出现这种错误。
②中括号不配对,由于形式不同,编译时出现error信息也不相同,主要在数组的定
义或引用中出现这种错误。
③圆括号不配对,由于形式不同,编译时出现error信息也各不相同,主要出现在需
要使用多层圆括号的复杂表达式中、函数调用中及带圆括号的语句中,像if、switch、for、
while、do-while等语句。如:
while((c=getchar()!='#')
putchar(c);
编译时出现error信息:
Callofnon-function
Whilestatementmissing)
系统认为调用了没有定义的函数并且while语句后的圆括号不配对。
(4)赋值格式错误。如有:
b++=a+7;
编译时将出现error信息:
Lvaluerequired
原因是赋值号“=”左边应该是变量,而不能是表达式。将表达式a+7的值赋给表达
式b++是不符合C语法规定的。另外,当对符号常量重新赋值时也会出现这种错误。如有:
#definePRICE30
PRICE=40;
编译系统也会认为“PRICE=40”这个赋值表达式不符合C语言的语法规定。因为PRICE
是一个符号常量,而不是变量。
(5)输入输出的数据的类型与所用格式说明符不一致。如:
inta=3;
floatb=4.5;
printf("%f%dn",a,b);
编译时并无出错信息,但运行结果将与原意不符,输出结果为
0.000000l6402
原因是系统并不是按照赋值的规则进行数据转换的(如把4.5转换成4),而是将数据在
存储单元中的具体存放形式按格式符的要求重新组织进行输出的。变量b在内存中占4个
字节,输出时只把最后两个字节中的数据按%d格式输出。这属于逻辑上的错误。输入输
·208·第10章常见错误和程序调试
出数据时一定要注意数据类型与格式说明一致,可改为
printf("%d%fn",a,b);
(6)scanf函数中忘记使用地址运算符&。如:
main()
{inta,b,sum;
scanf("%d%d",a,b);
sum=a+b;
printf("sum=%dn",sum);
}
编译时将出现warning信息:
Possibleuof'a'beforedefinition
Possibleuof'b'beforedefinition
原因是系统认为变量a、b在参加运算前没有被赋值。用scanf函数给变量输入数据,
是要把数据放到变量在内存所占的存储单元中去,如果不使用地址运算符&,就得不到变
量的地址,所以变量a、b就得不到数据。
这是许多初学者刚学习C语言时一个常见的疏忽,或者说是习惯性的错误。因为在其
他语言中输入数据时只需写出变量名即可(如BASIC语言中的INPUT语句),而C语言
要求必须指明“向哪个地址标识的单元送值”。应写成
scanf("%d%d",&a,&b);
另外,在程序中直接使用未赋值的变量时,也出现这样的警告信息。如有:
inta,b;
a=b;
(7)输入时数据的组织与要求不符。
用scanf函数输入数据,应注意输入数据的格式,如有:
scanf("%d%d",&a,&b);
有人按下面的方法输入数据:
3,4
这是错误的。可以用
printf("%d%d",a,b);
来验证一下,输出的结果与输入的原始数据是不同的。输入时数据间应该用空格(或Tab键,
回车键)来分隔,即应该用以下方法输入:
34
如果scanf函数为:
scanf("%d,%d",&a,&b);
格式字符串中除了格式说明符外,对其他字符必须按原样输入。因此,应按以下方法输入:
3,4
此时如果用“34”反而错了。还应注意,不能企图用
scanf("inputa&b:%d,%d",&a,&b);
想在屏幕上显示一行信息:
第10章常见错误和程序调试·209·
inputa&b:
然后在其后输入a和b的值,这是不行的,只能写成:
printf("inputa&b:");
scanf("%d,%d",&a,&b);
scanf与BASIC语言中INPUT语句的功能并不完全相同。
(8)混淆赋值号(=)与比较符(==)。如有:
if(a=b)printf("aequalb");
编译时将出现warning信息:
possiblyincorrectassignment
原因是系统认为程序中将一个赋值表达式“a=b”作为if语句的条件表达式。这种错
误在if、while或do-while语句中常见。也是学过BASIC语言常犯的错误,因为在BASIC
语言中,“=”既可作为赋值号,也可作为关系运算符“等于”,但在C中,“=”是赋值运
算符,“==”才是比较用的关系运算符“等于”。
(9)错误引用寄存器变量的地址。如有:
registerinta;
scanf("%d%d",&a,&b);
或有:
registerinta;
int*p;
p=&a;
编译时将出现error信息:
Musttakeaddressofmemorylocation
原因是地址运算符&只能取内存单元的地址,而不能取寄存器变量的地址。寄存器变
量没有地址。
(10)do语句中少了while。如有:
do
{sum=sum+n;
n++;
}
编译时将出现error信息:
Dostatementmusthavewhile
(11)do语句后while缺少分号
do
{sum=sum+n;
n++;
}while(n<=100)
编译时将出现error信息:
Do-whilestatementmissing;
(12)switch语句的各分支中漏写break语句。如有:
·210·第10章常见错误和程序调试
switch(score)
{ca5:printf("Verygood!");
ca4:printf("Good!");
ca5c3:printf("Pass!");
ca2:printf("Fail!");
default:printf("Dataerror!");
}
编译时并无错误,但运行结果不是企望的结果。上述switch语句的作用是希望根据
score(成绩)打印出评语。但当score的值为5时,输出结果为
VeryGood!Good!Pass!Fail!Dataerror!
原因是漏写了break语句。ca只起标号的作用,而不起判断作用,因此在执行完第
一个printf函数语句后接着执行第2、3、4、5个printf函数语句。解决的办法就是在每个
ca分支的后面加上一条break语句。
这样的错误在编译阶段是发现不了的,只有对执行结果进行分析后才能发现错误。
(13)对浮点数进行移位操作。如有:
main()
{floata,b=12.4;
a=b<<2;
printf("%f,%f",a,b);
}
编译时将出现error信息:
Illegaluoffloatingpoint
原因是对浮点数进行了不合法的运算。
3.数组类错误(Errorofarrayclass)
(1)直接引用没有定义的数组。如有:
main()
{inti;
for(i=0;i<10;i++)
scanf("%d",a[i]);
}
编译时将出现error信息:
Invalidindirection
原因是,C要求对程序中用到的数组必须先定义后使用,上面程序中没有对数组a进
行定义就直接引用了数组元素a[i]是无效。应当在函数体的开头加上数组定义:
inta[10];
这是学过BASIC语言的读者常犯的一个错误。在BASIC语言中,对元素个数在10
以下的数组可以不经定义直接使用。
(2)对数组进行动态定义。如有:
intn=10;
第10章常见错误和程序调试·211·
inta[n];
编译时将会出现error信息:
Constantexpressionrequired
原因是,C语言要求定义数组时数组的大小必须是常量或常量表达式。另外,定义二
维数组时如果将两个维数都放在一个中括号内,也会出现这种错误。如:
inta[3,5];
C语言把方括号中的“3,5”看成是一个逗号表达式,这也是学过BASIC语言的读
者常犯的错误。应改为:
inta[3][5];
即C语言中多维数组的每一维都要放到一个方括号中。
(3)引用数组元素时,下标越界。如有:
main()
{inta[10],inti;
for(i=1;i<=10;i++)
a[i]=i*i;
}
编译时并无错误,但这样做的后果可能非常严重。这是一些初学者常犯的错误,将数
组定义时的“元素个数”误认为是“可使用的最大下标值”。C语言规定,定义时用a[l0]
表示a数组有10个元素,下标从0开始,这10个数组元素是a[0]到a[9],因此引用元素
a[10]就超出a数组的范围了,但编译系统并不认为有错。因为系统并不对数组元素作下标
检查,所以只有靠程序设计人员来保证所引用的数组元素下标不越界,以避免可能对内存
中的其他数据造成破坏。
(4)定义数组或引用数组元素时误用了圆括弧。如有:
main()
{inti,a[10];
for(i=0;i<10;i++)
scanf("%d",&a(i));
…
}
编译时将出现error信息:
Callofnon-function
原因是系统认为调用了一个未定义的函数a(i)。同样,如果是在定义时出现了圆括号,
如:
inti,a(10);
编译时将出现error信息:
Functiondefinitionoutofplace
系统认为在函数内又定义了函数a(10),即出现了C语言禁止的函数嵌套定义。C语
言中定义数组或引用数组元素时必须用方括号,不要与BASIC语言混淆。
(5)误以为数组名代表数组中全部元素。如有:
·212·第10章常见错误和程序调试
main()
{inta[4]={1,3,5,7};
printf("%d%d%d%dn",a);
}
编译时并无错误,但得到的是不可预知的结果。原因是企图用数组名代表数组的4个
元素,但在C语言中,数组名只能代表数组的首地址,并不代表数组的全部元素。应改为:
printf("%d%d%d%dn",a[0],a[1],a[2],a[3]);
(6)混淆字符数组与字符指针的区别。如有:
charc[20];
c="Hello!";
编译时将出现error信息:
Lvaluerequired
原因仍然是系统认为赋值错误。C语言中,字符数组名虽然代表数组的首地址,但它
是一个常量,不能被赋值。应弄清字符数组与字符指针变量用法的区别。如要对字符数组
赋值,可采用以下方法:
①在定义字符数组的同时初始化。如:
charstr[20]="Hello!";
②用字符串函数strcpy()。如:
charstr[20];
strcpy(str,"Hello!");
③用字符型指针变量。如:
char*p;
p="Hello!";
4.函数类错误(Errorsoffunctionclass)
(1)用传统方式定义函数时把形参与函数中的局部变量一起定义。如:
max(x,y)
intx,y,z;
{z=x>y?x:y;
returnz;
}
编译时将出现error信息:
'z'notanargument
undefinedsymbol'z'
原因是系统认为z既不是函数max的参数也不是局部变量。函数的形参与函数内部的
局部变量定义位置是不同的,形参应该放在函数体之前定义,而局部变量则应该放在函数
体中的说明部分进行定义。因此程序应改为:
max(x,y)
intx,y;
{intz;
第10章常见错误和程序调试·213·
z=x>y?x:y;
returnz;
}
(2)所调用的函数定义在调用语句之后,但在调用之前未加声明。如有:
main()
{floatx,y,z;
x=2.5;y=-5.6;
z=max(x,y);
printf("%fn",z);
}
floatmax(floatx,floaty)
{returnx>y?x:y;
}
编译时将出现error信息:
Typemismatchinredeclarationof'max'
原因是系统认为'max'类型不对。因为max函数是实型的,定义在main之后,但在main
函数调用此函数前时却没有声明,系统默认为是int类型的。C语言中,对被调函数定义在
主调函数之后的,除int类型的函数可以不用声明外,其它都要声明。纠正错误的方法有
两种:
①在main函数中增加对max函数的声明。
floatmax(floatx,floaty);
②把max函数的定义放到main函数之前。
(3)不要在函数的定义与函数的声明中混用新旧两种模式。如有:
main()
{floatfun(float,float);
…
}
floatfun(x,y)
floatx,y;
{…}
编译时将出现
Typemismatchinparameter
系统会认为函数max的参数类型不匹配。
(4)错误理解函数的参数传递。如有:
voidswap(intx,inty)
{intt;
t=x;x=y;y=t;
}
main()
·214·第10章常见错误和程序调试
{inta,b;
a=35;b=4;
swap(a,b);
printf("%d,%dn",a,b);
}
编译时并没有错误,但得不到预期的结果,因为main函数调用swap函数属于传值调
用,参数传递是单向的,形参的值发生变化是不会影响实参的,所以程序想通过调用swap
函数达到a和b交换的目的是实现不了的。
如果想使实参的值受到形参的影响,应该用数组名或指针变量作函数的参数,即用地
址传递的方式。这样形参的值在被调函数中发生的变化会影响到实参的值,如:
voidswap(int*pt1,int*pt2)
{intt;
t=*ptl;*ptl=*pt2;*pt2=t;
}
main()
{inta,b,*p1,*p2;
a=3;b=4;
pl=&a;p2=&b;
swap(p1,p2);
printf("%d,%dn",a,b);/*a和b的值已交换*/
}
但如果将swap函数定义成:
voidswap(int*pt1,int*pt2)
{int*p;
p=ptl;
ptl=pt2;
pt2=p;
}
同样也实现不了交换两个数据的目的。一定要理解函数间参数传递的实质,都是单向
的“值传递”。
(5)函数的实参和形参类型不一致。
main()
{inta=3,b=4,c;
c=fun(a,b);
…
}
fun(floatx,floaty)
{
…
第10章常见错误和程序调试·215·
}
编译时并无错误,但得不到正确的结果,因为实参a、b为整型,形参x、y为实型。
C要求实参与形参的类型、个数、顺序一致。
(6)混淆一般表达式与函数参数的计算次序。如有:
inti=5;
printf("%d,%d,%dn",i,++i,++i);
许多人认为输出必然是
5,6,7
其实不然。在TurboC系统中输出的是:
7,7,6
因为在该系统中,函数参数的计算次序是从右到左的。先求出最右面一个参数(++i)
的值为6,再求出第2个参数(++i)的值为7,最后求出最左面的参数(i)的值也是7。而一般
表达式的计算次序是从左到右的。如有:
i=3;
k=(i++)+(i++)+(i++);
不少人认为k的值为12即k=3+4+5,实际上k的值为9,因为系统先将i的值取出来
作为表达式的值进行加法运算3+3+3,然后再实现自加,即“先用后加”,最后i的值变为
6。
C语言没有具体规定函数参数求值的顺序是从左到右还是从自右到左。但每个C编译
程序都有自己的顺序,这就要求在使用中应当避免出现这种容易引起不同理解的用法,以
免使程序的通用性受到影响。如果在上例中,希望输出“5,6,7”时,可以改用
i=5;j=i+1;k=j+1;
printf("%d,%d,%dn",i,j,k);
(7)引用void类型的函数值。如有:
voidmax(intx,inty)
{returnx>y?x:y;
}
编译时出现error信息:
Typemismatchinredeclarationof'max'
因为函数的类型定义为void后,系统将禁止函数返回任何值。
5.指针类错误(Errorsofpointerclass)
(1)引用没有初始化的指针变量。如有:
int*p;
*p=10;
编译时将出现warning信息:
possibleuof'p'beforedefinition
原因是虽然定义了指针变量p,但是没有明确指针变量p的指向,直接引用可能会破
坏其它内存单元中的值,甚至造成系统破坏。可以改成:
int*p,a;
·216·第10章常见错误和程序调试
p=&a;
*p=10;
先将一个变量的地址赋给指针变量p,然后就可以对p进行操作了。
(2)混淆指针量与非指针量的区别。如有:
int*p;
p=100;
编译时将出现warning信息:
Non-portablepointerassignment
原因是系统认为程序将一非指针的量赋给一个指针量。因为指针变量只能存放地址,
不能将一个整型量(或非地址类型的数据)赋给一个指针变量。同样若将一个指针量给一
个非指针量时也会出现这样的警告。如:
inta,*p;
a=p;
(3)不同类型的指针混用。如有:
main()
{inti=3,*p1;
floata=1.5,*p2;
pl=&i;p2=&a;
p2=p1;
printf("%d,%dn",*p1,*p2);
}
编译时将出现warning信息:
Suspiciouspointerconversion
原因是系统认为程序中出现可疑的指针类型转换。因为在“p2=p1”中,p1、p2的基
类型不同,C不允许指针变量指向与它类型不同的数据。如果确实需要,必须进行强制类
型转换。应改成:
p2=(float*)pl;
这种情况在C程序中动态分配内存空间时是最常见的。例如,用malloc函数开辟内存
空间,malloc函数返回的是不指向任何类型数据的指针(void*),而人们希望开辟的是能
够存放一个结构体类型变量的值的存储空间。要得到指向该结构体类型变量的指针,必须
进行类型转换。如有:
#defineLENsizeof(structstudent)
structstudent
{intnum;
charname[20];
floatscore;
};
structstudent*p;
p=(structstudent*)ma11oc(LEN);
第10章常见错误和程序调试·217·
…
p是指向structstuden结构体类型数据的指针,将malloc函数返回的void*类型指针
转换成指向structstudent类型变量的指针。
(4)混淆数组名与指针变量的区别。如有:
main()
{inti,a[6];
for(i=0;i<6;i++)
scanf("%d",a++);
…
}
编译时将出现error信息:
Lvaluerequired
原因是系统认为a++进行了错误的赋值运算。虽然数组名代表数组的首地址,它是一
个不能改变的常量,当然不允许做a++运算。而指针变量则可以,应改为:
inti,a[6],*p;
p=a;
for(i=0;i<6;i++)
scanf("%d",p++);
或
int*p,a[6];
scanf("%d",p);
6.文件操作类(Erroroffileoperationclass)
使用文件时忘记打开或打开文件方式不对。如:
if((fp=fopen("test","r"))==NULL)
{printf("cannotopenthisfile\n");
exit(0);
}
ch=fgetc(fp);
while(ch!='#')
{ch=ch+4;
fputc(ch,fp);
ch=fgetc(fp);
}
对以“r”方式(只读方式)打开的文件,进行既读又写的操作,显然是错误的。
此外,有的程序常忘记关闭文件,虽然系统会自动关闭所用文件,但可能会丢失数据。
因此必须在用完文件后关闭它。
以上只是列举了一些初学者常出现的错误,这些错误大多是对于C语法不熟悉之故。
对C语言使用多了,比较熟练了,犯这些错误自然减少了。在深入使用C语言后,还会出
·218·第10章常见错误和程序调试
现其他一些更深入、更隐蔽的错误。
10.2程序调试(Programdebug)
所谓程序调试是指对程序的查错和排错。写完一个程序只能说完成了任务的一半(甚至
不到一半)。调试程序往往比写程序更难,更需要精力、时间和经验。有时一个小小的程序
会出错五六处,而发现和排除一个错误,有时竞需要半天,甚至更多。希望读者通过实践
掌握调试程序的方法和技术。
调试程序一般应经过以下几个步骤:
1.人工检查(静态检查)
在写好一个程序以后,不要匆匆忙忙上机,而应对纸面上的程序进行人工检查,这能
发现编写程序中的一些错误。为了更有效地进行人工检查,编写程序时应注意力求做到以
下几点:
(1)采用结构化程序方法编程,使程序结构清晰;
(2)尽可能多加注释,对每段程序及主要变量进行说明,增加程序可理解性;
(3)采用模块化结构,不要将全部语句都写在main函数中,要善于利用函数定义及
函数调用,用一个函数(模块)来实现一个单独的功能。这样既易于阅读也便于调试。
2.上机调试
通过上机发现错误称动态检查。在编译时给出语法错误的信息(包括哪一行有错以及错
误类型),可以根据提示的信息具体找出程序中出错之处并改正之。应当注意的是:有时提
示的出错行并不是真正出错的行,如果在提示出错的行上找不到错误的话,应当到上一行
再找。另外,有时提示出错的类型并非绝对准确,由于出错的情况繁多而且各种错误互有
关联,因此要善于分析,找出真正的错误,而不要只从字面意义上死抠出错信息,钻牛角
尖。
如果系统提示的出错信息多,应当从上到下逐一改正。有时显示出一大片出错信息往
往使人感到问题严重,无从下手。其实可能只有一二个错误。例如,若对一个变量未定义,
编译时就会对所有使用该变量的语句都发出出错信息。只要加上一个变量定义,所有错误
就都消除了。
3.分析程序运行结果
在改正语法错误(包括“错误”(error)和“警告”(warning))后,程序经过连接(1ink)就
得到可执行的目标程序。运行程序、输入程序所需数据,就可得到运行结果。应当对运行
结果进行分析,看它是否符合要求,为此要选择一些模拟数据进行测试。有的初学者看到
输出运行结果就认为没问题了,不作认真分析,这是危险的。
运行结果不对,大多属于逻辑错误,对这类错误往往需要仔细检查和分析才能发现。
可以采用以下办法:
(1)将程序与流程图仔细对照。如果流程图是正确的话,程序写错了,是很容易发
现的。例如,复合语句忘记写花括号。只要一对照流程图就能很快发现。
(2)如果实在找不到错误。可以采取分段检查的方法。在程序不同位置设几个printf
函数语句,输出有关变量的值,逐段住下检查直到找到在某一段中数据不对为止。这时就
已经把错误局限在这一段中了,不断缩小查错区,就可能发现错误所在。
第10章常见错误和程序调试·219·
(3)也可以用前面介绍过的“条件编译”命令进行程序调试。
(4)如果在程序中没有发现问题,就要检查流程图有无错误,即算法有无问题,如
有则改正之,接着修改程序。
(5)利用系统提供的调试工具,跟踪流程并给出相应信息,使用更为方便。
总之,程序调试是一项细致深入的工作,需要下功夫、动脑子、善于累积经验。希望
读者能给以足够的重视。
本文发布于:2023-02-02 02:13:00,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/88/175939.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |