《《C语言程序设计》第十章 程序常见错误分析.pdf》由会员分享,可在线阅读,更多相关《《C语言程序设计》第十章 程序常见错误分析.pdf(13页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、 第十章第十章 程序常见错误分析程序常见错误分析 127第十章 程序常见错误分析 C 语言是一种方便灵活、功能性很强的程序设计语言,但是对于初学者很难掌握,尤其是出了错还不知道错误在哪儿,这是由于 c 编译程序对语法的检查不如其他高级语言那样严格,往往要求设计者自己设法保证程序的正确性,因此调试一个c 语言程序是不容易的,需要设计者不断积累经验,提高程序设计和调试的水平。本章将初学者容易犯的错误集中列举出来,供初学者参考,另外,还介绍了调试程序的一般方法,以便于初学者提高调试程序的能力。10.1 常见错误分析常见错误分析 程序出错通常有两种情况:一、语法错误。这种错误通常 c 语言编译程序会给
2、出“出错信息”,并且告诉你在哪一行出错,程序 设计者只要细心,会很快发现并排除这类错误。二、逻辑错误。这种错误通常 c 语言编译程序不会检查出来,因此它比语法错误更难检查,这就要求 程序设计者必须要有丰富的经验,才能很快的解决。有时可能设计者忘记加某种符号,例如,复合语句的花括号,而导致程序出错,这种错误经过仔细检查,还是可以发现的。但是,如果程序从算法上就有问题,那修改和检查起来就非常困难了,像这种错误,在这里不作赘述。下面将程序中常见的错误列举出来,供初学者参考,并以此为鉴。1 没有定义变量没有定义变量 在 c 语言程序中,变量一般是先定义后使用。例如:main()int x=10,y=2
3、;s=x*y+3;printf(“%dn”,s);编译后,编译程序在程序第三行给出出错信息“Undefined symbol s in function main”,意思是在 main 函数中第三行有未定义的符号s,如果在程序开头这样写“int x=10,y=2,s;”上面的程序就顺利通过编译程序了。再如:第十章第十章 程序常见错误分析程序常见错误分析 128main()int x=10;y=2;printf(“%dn”,x*y+3);编译后,编译程序在程序第一行给出出错信息“Undefined symbol y in function main”,在定义类型一致的多个变量时,变量之间用逗号隔
4、开,如果用分号隔开说明这句定义语句结束,后面的变量就没有被定义,如果将“y2;”这句前的分号改成逗号,程序就不会出错了。这种错误是初学者最爱犯的错误。2 未注意数据的数值范围未注意数据的数值范围 各种不同类型的数据类型表示数的范围不同,例如,int 类型的数占 2 个字节,表示范围32768 32767,如果所表示的数超过这个范围,结果就会出错,如下面的程序段:int num;num=89101;printf(“%d”,num);程序运行结果是 23565,而不是 89101,原因是 89101 已超过了 32767,两个字节容不下 89101,则将高位截取。见图 12.1 89101:235
5、65:3 输入输出的数据类型与所用格式说明符不一致输入输出的数据类型与所用格式说明符不一致 输入输出的数据类型应与所用格式说明符匹配,否则将会导致运行结果与原意不符。例如下面的程序段:int a=3;float b=4.5;printf(“%f,%dn”,a,b);编译时不会给出出错信息,但运行结果为:0.000000,16402 它们并不是按照赋值的规则进行转换(如把 4.5 转换成 4),而是将数据在存储单元中的形式按格式符的要求组织输出(如 b 占 4 个字节,只把最后两个字节中的数据按d 作为整数输出)。另外,有符号的数如果按照无符号的数输出,也将会导致结果出错,反之依然。4 输入变量
6、时忘记使用输入变量时忘记使用&符号符号 很多初学者都习惯这样写,例如:scanf(“%d”,a);00 00 00 00 00 00 00 0101 01 11 0000 00 11 0101 01 11 0000 00 11 01 第十章第十章 程序常见错误分析程序常见错误分析 129c 语言要求是向变量的地址送值,所以应写成 scanf(“%d”,&a);5 误把“”当成“”误把“”当成“”比较符比较符 例如:int a;scanf(%d,&a);if(a=1)printf(a%dn,a);程序运行结果将是无论 a 为什么值,打印结果都将是 1,而程序本意是 a 等于 1 时,才将 a 值
7、打印出来。原因是“if(a=1)”中 a 被赋值为 1,那么条件表达式的值始终都为真,所以无论输入的 a 为什么值都将打印输出“a1”。因此“”是赋值号,“”是关系比较符,两者不能混谈。这种错误在编译时往往不会检查出来,但运行结果往往是错的,而程序设计者由于习惯的原因,自己通常是不易察觉的。6 分号引起的错误分号引起的错误 1)语句后面漏分号语句后面漏分号 c 语言要求在一条语句结束后以“;”为结束标记。若忘记加了,则程序在编译时,会给出这样的 出错信息“Statement missing;in function main”。2)在不该加分号的地方加了分号在不该加分号的地方加了分号 例如:if
8、(x0);printf(“%5d”,x);该程序段本意是当 x 为正数时则打印输出。但由于在“if(x0)”后面加了一个分号;,因此 if 语句到此结束,即当 x0 时执行一个空语句,那么无论x 是否为正数,都将打印输出。又如:sum0;for(i=1;i11;i+);sum+=i;该程序段本意是计算 1210 的和,sum55,但是实际运行结果为11,原因是在“for(i=1;i11;i+)”语句后加了一个分号;,使循环体变成了空语句,导致最后累加的是 sum01111 的结果。总之,在 if、for、while 语句中,不要画蛇添足多加分号。7 花括弧不成对或忘记加引起的错误花括弧不成对或
9、忘记加引起的错误 例如:sum=0;i=1;while(i=10)sum+=i;第十章第十章 程序常见错误分析程序常见错误分析 130 i+;程序段的本意是计算 1210 的和,但上面的语句只是重复执行 sumsum1 的操作,并且循环永不停止,原因是 i 的值始终没有改变,因此该循环结构没有使循环趋于结束的语句。该程序段错误在循环体是一个复合语句,没有加花括弧,应改为:while(i=10)sum+=i;i+;在程序设计时,有时需要花括弧的地方太多,但由于程序设计者的马虎,很容易造成花括弧不成对的现象,这时编译程序会给出出错信息,“Compound statement missing in
10、function main”(意为在 main 函数中复合语句缺少),这时只要设计者仔细,这种错误会很容易解决。8 括弧不成对括弧不成对 当一个语句使用多层括弧时常出现这类错误,纯属粗心所致,如:while(c=getchar()!=#)putchar(c);少了一个右括弧。9 忘记了标识符的大小写字母的区别忘记了标识符的大小写字母的区别 在标识符中,大小写字母是有区别的,例如在程序中“int num;”定义一个 num变量,但在使用时,却误用成 Num,这时编译出错,编译程序会把 num 和 Num 看成两个不同的变量名处理,并且给出出错信息“Undefined symbol Num in
11、function main”。10 Switch 语句各分支中漏写语句各分支中漏写 break 语句语句 例如:switch(week)case 1:printf(“Monday!”);case 2:printf(“Tuesday!”);case 3:printf(“Wednesday!”);case 4:printf(“Thursday!”);case 5:printf(“Friday!”);case 6:printf(“Saturday!”);case 7:printf(“Sunday!”);上面的程序段是根据 week 的值,打印出对应的星期。但当 week 值为 3 时,输出:Thur
12、sday!Friday!Saturday!Sunday!原因是漏写了 break 语句。Case 只起标号作用,而不起判断作用,因此应在第 1、2、3、4、5、6 个 printf 语句后加一句“break;”。11 混淆字符和字符串的表示方法混淆字符和字符串的表示方法 第十章第十章 程序常见错误分析程序常见错误分析 131字符是用单引号括起来的,并且是单个字符,字符串是用双引号括起来的。初学者在写程序时,往往 忽略了这一点,而导致程序出错。12 使用自加()和自减()运算符时出的错误使用自加()和自减()运算符时出的错误 例如:main()int*p,a5=2,4,6,8,10;p=a;pr
13、intf(“%dn”,*p+);不少人认为“*p+”是先使 p 自加 1,即指向第一个元素 a1,然后输出第一个元素 a1的值为 4。但实际上是先执行“p”,即先引用 p 的原值,然后 p 再自加 1,p 的原值指向数组 a 的第 0 个元素 a0,即*p=2,因此执行结果就是先输出 a0的值,p 再自加 1。如果是*(+p)结果又不一样了,输出的是 a1的值。13 引用数组元素时误用了圆括弧引用数组元素时误用了圆括弧 c 语言规定,对数组的定义和引用数组元素时必须用方括弧,而不能用括弧。14 没有弄清定义的数组下标的最大使用临界值没有弄清定义的数组下标的最大使用临界值 c 语言规定,如定义
14、a10,表示 a 数组中有 10 个元素,只能使用 a0到 a9单元,不能使用最大下 标值为 10 的单元,即 a10。一般在编程时容易犯这样的错误,如:main()static int a10=1,2,3,4,5,6,7,8,9,10;int i;for(i=1;i=10;i+)printf(“%d”,ai);想输出 a1到 a10的值,但数组初始化只给 a0到 a9赋值,并未对 a10赋值。这种错误通常编译程序不会给出出错信息。15 误以为数组名代表数组中的全部元素误以为数组名代表数组中的全部元素 例如:main()int a4;scanf(“%d”,a);企图用数组名代表整个数组,用“s
15、canf(“%d”,a);”对该数组中的 4 个单元进行赋值。但 c 语言规定,数组名代表该数组的首地址,即“scanf(“%d”,a);”只对 a0单元赋了值,而其余单元没有赋值。第十章第十章 程序常见错误分析程序常见错误分析 132 通常这种错误编译程序是不会检查出来的。16 混淆字符数组和字符指针的区别混淆字符数组和字符指针的区别 例如:main()char str20;str=“hello,world!”;printf(“%sn”,str);编译程序给出出错信息“Lvalue required in function main”。原因是 str 代表数组的首地址,在编译时对 str 数
16、组分配了一段内存单元,因此在程序运行期间 str 是一个常量,不能再被赋值。所以 str“hello,world!”是错误的。若“char str20;”改成“char*str”,则程序正确,原因是此时的 str 是指向字符数据的指针变量,str“hello,world!”是合法的,它将字符串的首地址賦给指针变量 str,然后再 printf函数语句中输出字符串“hello,world!”。17 混淆函数中的形参和局部变量的定义方式混淆函数中的形参和局部变量的定义方式 函数中的形参是在函数体之前定义,而函数中用到的局部变量应在函数体中定义。18 所调用的函数在调用语句之后才定义,而又在调用前未
17、加以说明所调用的函数在调用语句之后才定义,而又在调用前未加以说明 例如:main()float x,y,z;x=2.4;y=5.6;z=min(x,y);printf(“%fn”,z);float min(x,y)float x,y;float z;return(z=xy?x:y);编译时出错。原因是 min 函数是实型的,但在 main 函数之后才定义,所以编译程序在读到“z=min(x,y);”发生出错,改错方法有两种:一是在 main 函数开头增加一个对 min 函数的说明,即:main()float x,y,z;float min();x=2.4;y=5.6;z=min(x,y);第十
18、章第十章 程序常见错误分析程序常见错误分析 133printf(“%fn”,z);二是将 min 函数的定义位置调到 main 函数之前。即:float min(x,y)float x,y;float z;return(z=xy?x:y);main()float x,y,z;x=2.4;y=5.6;z=min(x,y);printf(“%fn”,z);19 没有弄清实参值和形参值之间的传递方向没有弄清实参值和形参值之间的传递方向 c 语言中实参变量和形参变量之间的数据传递是单向的“值传递”方式。例如:void fun(x,y)int x,y;int t;t=x;x=y;y=t;main()in
19、t a=3,b=4;swap(a,b);printf(“%d,%dn”,a,b);该程序的原意是要通过调用 swap 函数将 a、b 的值互换,但实际打印的结果仍然是“3,4”。原因就是形参的值的改变并不会影响实参的值的改变,要想达到这一目的,可以用指针变量。指针变量作为函数的参数虽然也不能改变实参指针变量的值(即地址),但可以改变实参指针变量所指变量的值。因此,该程序可改为:void fun(p1,p2)int*p1,*p2;int t;t=*p1;*p1=*p2;*p2=t;main()int a=3,b=4,*p,*q;p=&a;q=&b;第十章第十章 程序常见错误分析程序常见错误分析
20、134 swap(p,q);printf(“%d,%dn”,a,b);20 函数的实参和形参类型不一致函数的实参和形参类型不一致 函数的实参和形参类型要一致,并且要一一对应,否则程序结果将会出错。21 不同类型的指针混用不同类型的指针混用 例如:main()int a=3,*p1;float*p2;p1=&a;p2=p1;printf(“%d,%dn”,*p1,*p2);本意是 p1 和 p2 都指向 a 变量,但 p2 是指向实型变量的指针,不能指向整型变量。指向不同类型的指针间的赋值必须进行强制类型转换,如:p2=(float*)p1;通常这种错误编译程序是不会检查出来的,但会影响程序的运
21、行结果。22 没有注意函数参数的求值顺序没有注意函数参数的求值顺序 例如:main()int i=3;printf(“%d,%d,%d”,i,+i,+i);不少人认为输出结果是 3,4,5 但实际上输出结果却是 5,5,4 因为许多系统是采用的自右至左的顺序求函数参数的值,即先求出最右面的一个参数(i)的值为 4,再求出第二个参数的值(i)的值为 5,最后最左面的参数的值是 5。C 标准没有具体规定参数求值的顺序是自左至右还是自右至左,但每个 c 编译程序都有自己的顺序,在有些情况下,从左至右求解和从右至左求解的结果是一样的,所以并不明显的影响程序的运行结果。在编写程序的时候应该注意这一点。如
22、果在上例中,希望输出“3,4,5”时,可以这样改:int i=3;j=i+1;k=j+1;printf(“%d,%d,%d”,i,j,k);23 混淆数组名和指针变量的区别混淆数组名和指针变量的区别 例如:第十章第十章 程序常见错误分析程序常见错误分析 135main()int i,a5;for(i=0;i5;i+)scanf(“%d”,a+);c 语言规定,数组名代表数组的首地址,它的值时不能改变的,因此不能作自加或自减运算,所以该程序企图用 a,使指针下移,指向输入数据的数组元素,这一点是错误的。要想达到这一目的,可以通过指针变量,如:main()int i,a5,*p;p=a;for(i
23、=0;i5;i+)scanf(“%d”,p+);24 混淆结构体类型和结构体变量的区别混淆结构体类型和结构体变量的区别 例如:struct student long int num;char name20;char sex;int age;student.num=20014;strcpy(student.name,“zhang min”);student.sex=M;student.age=19;这种使用结构体是错误的。Student 在这里是结构体类型,要使用这个类型,必须先定义一个结构体变量,才能对其中的成员赋值。如:struct student stu;stu.num=20014;str
24、cpy(stu.name,“zhang min”);stu.sex=M;stu.age=19;25 使用文件时忘记打开,或打开方式和使用情况不匹配使用文件时忘记打开,或打开方式和使用情况不匹配 文件的打开方式是要根据程序的具体要求,如果程序只从文件读出数据,就应该用只读方式打开,如果只向文件输出数据,就应该用只写方式打开。若“r”方式(只读方式)打开的文件,进行既读又写的操作,显然是不可以的。例如:第十章第十章 程序常见错误分析程序常见错误分析 136 if(fp=fopen(“test”,”r”)=NULL)printf(“cannot open this filen”);exit(0);c
25、h=fgetc(fp);while(ch!=#)ch+=4;fputc(ch,fp);ch=fgetc(fp);另外,有的程序常忘记关闭文件,虽然系统会自动关闭所用文件,但可能会丢失数据,因此必须在用完文件后关闭它。10.2 程序调试程序调试 调试程序对于程序设计来说是一个非常关键的环节,往往比写程序还难。有的初学者往往写完一个程序就认为万事大吉了,还有的只要自己编写的程序能够编译通过,生成 exe 文件,就认为自己设计的程序成功了,他没有考虑到运行的结果是否正确,以及编写的程序是否稳定等等。以上说的情况,在程序设计时经常发生,那么程序调试究竟是什么?简单的说程序调试是程序的查错和排错,最终通
26、过编译连接生成合乎要求的 exe 文件。调试程序一般需要经过以下几个步骤:一、人工检查 这一步往往容易被人忽视,很多人一写完程序就急于上机调试,总认为程序的错误计算机都会检查出 来,这样,一是计算机负担很重,二是程序出现的逻辑错误不容易被发现,比如不符合流程图的语句结构,忘了花括弧等等。一般编译程序只是检查程序的语法错误,对逻辑错误是视而不见的,所以要编写好一个程序,人工检查这一步是不可缺少的。为了能更好的人工检查,所编的程序一般要力求做到以下几个方面:1 程序应当采用结构化程序方法编程,以增加可读性;2 尽可能多加注释,以便于程序的理解;3 尽量多使用函数,不要将语句都写在 main 函数中
27、,一个函数实现一个功能,各函数之间除用参数传递数据,数据间尽量少出现耦合关系,这样便于阅读和检查。二、上机调试 人工检查程序无误之后,就可以上机调试了。编译程序可以给出程序的语法错误,并且能够给出程序 错在哪一行以及错误的类型,这样便于程序设计者找出程序的错误并改之。但是有时提示的出错行并不是真正出错的行,需要在出错的上一行或下一行进行查找,比 第十章第十章 程序常见错误分析程序常见错误分析 137如花括弧的错误,往往是在出错的上一行少了一个花括弧。另外,还有一种可能,就是给出的提示出错信息并非是准确的,由于出错的情况繁多而且各种错误互有联系,因此要善于分析,找出真正的错误,而不要对提示信息钻
28、牛角尖。有时一个程序编译后,系统给出的出错信息很多,这时设计者不要慌乱,应该将程序从头至尾的检查,也许程序只有一两个错误。一般改正完错误后再编译,直到编译成功(success)为止。另外,有一种错误并不是程序本身的语法错误,但编译总是通不过,出错信息可能是“User Break”或“Unable to open include file”,这种错误往往出在 turbo c 的环境配制上,因为c 语言编译连接时需要系统提供的包含文件以及一些库文件等,系统应该提供这些文件所在的正确路径,否则程序编译时将会出错。另外,turbo c 在编译连接后,将生成的 exe 文件,并直接按系统环境配制的路径存
29、放,如果没有给出正确的存放路径,编译程序将会给出这样的错误“Unable to create output file”。所有的配制应该在图 10.1 的位置给出,配制完以后,需要选择 options/save options 选项,将刚才配制好的环境以 tcconfig.tc 为文件名存放。输出文件路径 包含文件路径 lib 库文件路径 图 10.1 turbo c 环境配制 三、检查运行结果 程序经过上机调试后,编译连接生成可执行文件(exe 文件)。但这并不意味着程序的编写成功了,还 需要对程序的运行结果进行检查,如果运行结果有误,应该重新检查程序。一般运行结果有误,大多属于逻辑错误,应该
30、将程序和流程图仔细对照,如果流程图正确,程序有误的话,这种错误是很容易改正的,但如果是流程图有误的话,就应该重新 第十章第十章 程序常见错误分析程序常见错误分析 138考虑程序的思路,这样改起来比较麻烦些。检查运行结果不光只是检查一次,要经过反复的检查,“试验数据”最好是程序有可能出现的各种情况的数据,比如,输入 A、B、C 三个整数,判断其是否构成三角形,若能则计算出该三角形的面积,并确定它是等边还是等腰或是其他三角形,在这个程序检查结果时,既要设计一组等腰、等边三角形的数据,还要设计一组其他三角形的数据,更重要的是要设计一组不能构成三角形的数据。不少人在设计程序时,本生就没有考虑到后一种情
31、况,对于程序来说,这是不足之处,应该修改程序。所以不光是程序设计要考虑周全,而且检查结果时要方方面面,以检查程序是否符合题意。有时在检查结果时,程序本生没有什么错误,但是在运行结果上就不那么令人满意。比如,计算 n!,如果在程序里定义存放 n!的值的变量为 int 类型的数,那么在检查运行结果时,当输入的 n 值大于等于 8 时,n!就为负数,这显然是不正确的,那么是不是程序设计上的错误呢?不是,原因是 int 类型的数是有范围的(-3276832767),如果存放的数据超过这个范围,就发生溢出,即数据发生错误。这种情况下,程序本生没有错误,只是无论定义存放 n!的值的变量为任何类型,总会发生
32、溢出现象,因为 c 语言任何类型的数据都有一定范围,不能表示无限大的数,所以计算 n!的 n 值不能想取多大就多大,但程序设计者应该尽量将计算 n!的 n 值取得更大一些。另外,有时需要采用单步执行程序来检查程序的错误。单步执行程序是一句一句的执行程序,在单步执行程序过程中,很有必要对程序中的某个关键变量进行跟踪检查,以便查出程序是在哪个地方出错的。在 turbo c 中,单步执行是选择 RunStep over 选项,或 F8 键,然后每按一下 F8 键就单步执行程序一句,直到执行完为止。跟踪检查某个变量步骤是,选择 Break/WatchAdd watch 选项,出现一对话框,填写变量的名
33、称(见图 10.2),然后系统会自动在 turbo c 软件的最下方加一个 watch 窗口,窗口里有跟踪检查的变量,单步执行程序可以观察这些跟踪变量的值的变化情况。分析跟踪变量的值的变化,可以查出程序的错误之处。这种方法在调试程序的时候经常用到,而且很方便的检查出程序的错误。总之,调试程序是一项细致深入的工作,需要下功夫、动脑经,并且要善于积累经验。同时,程序调试也是一项艰辛的工作,常常一个程序可能要调试很长一段时间,这就要考验设计者的耐心,如果你急于求成,反而得不到理想的程序。上机调试程序的目的不光是得到一个“正确的程序”,还要掌握调试程序的方法和技术,提高调试程序的能力。第十章第十章 程序常见错误分析程序常见错误分析 139 图 10.2 watch 窗口