C语言常见错误分析.ppt

上传人:tia****nde 文档编号:12706175 上传时间:2020-05-14 格式:PPT 页数:115 大小:571.50KB
返回 下载 相关 举报
C语言常见错误分析.ppt_第1页
第1页 / 共115页
C语言常见错误分析.ppt_第2页
第2页 / 共115页
C语言常见错误分析.ppt_第3页
第3页 / 共115页
点击查看更多>>
资源描述
C语言常见错误及问题分析,C语言编程常见错误分析,C语言是目前世界上最通用的编程语言之一,也是目前研发使用最多的编程语言。同各种各样的bug作斗争,是每一个C程序员每天所面临的课题。本文将从微观角度出发,对一些常见出错类型的案例进行分析,希望大家今后能避免类似的错误。,数字和表达式错误变量的错误数组和指针的错误逻辑和流程的错误,数字和表达式的错误,运算符和优先级的错误字节序的错误魔鬼数字宏定义的错误sizeof的错误,最常见的运算符错误就是“=”和“=”的误用,intmain()intret;ret=GetVars();if(ret=VOS_OK).return0;,错误后果:1.变量被错误赋值。2.逻辑判断不正确。建议和结论:尽管是初级bug但是还是常有发生,建议写成“VOS_OK=ret”的形式,这样在编译的时候即可发现这种错误。,“+”和“-”在表达式中的应用,#definemypower(a)(a)*(a)intmain()inti=1,j=2;j=mypower(+i);printf(rni=%dj=%d,i,j);return0;,错误后果:结果与期望的不一致建议与结论:1.对于“+”和“-”这种基本的知识还是应该掌握的2.自增和自减变量在本表达式中不要再引用,否则可能依赖编译器实现3.没有把握的用法千万不要用,否则可能导致意想不到的错误,优先级问题也是编码初期容易出现的问题,C语言有众多的运算符号,它们之间的优先级关系非常复杂,即使是一个熟练的C程序员,要清楚地记住这些优先级关系也绝非易事。if(highif(highif(a|b)UCHARszIP4;stIPAddr;如果赋值:stIPAddr.szIP0=192;stIPAddr.szIP1=168;stIPAddr.szIP2=0;stIPAddr.szIP3=1;则大端和小端上stIPAddr.ulIP的值不同:大端:stIPAddr.ulIP=0 xC0A80001;小端:stIPAddr.ulIP=0 x0100A8C0;,二.指针强转:定义如下变量:ULONGulTool=0 x12345678;USHORT*pusTool=(USHORT*)则在大端和小端上,*pusTool和*pucTool的值是不同的。大端系统:*pusTool=0 x1234*pucTool=0 x12小端系统:*pusTool=0 x5678*pucTool=0 x78,建议和结论:1.填写报文和解析报文时一定要注意字节序问题,使用相应的宏操作(htonl、ntohl等)2.当取变量的一部分值时,如联合体、强制指针转换等,需要考虑字节序问题3.互通测试很重要,魔鬼数字问题,魔鬼数字是指直接使用数字,而不是使用预先定义好的宏、常量、枚举等,这是一种不好的编程习惯。如下:,l=round/3.14159;DeadInt=HelloInt*4;pstPack=malloc(36);pTcp=pIp+20;,constdoublePI=3.14159;l=round/PI;#defineDEADTIME4DeadInt=HelloInt*DEADTIME;#definePACKSIZE36pstPack=malloc(PACKSIZE);#defineIPHEADLEN20pTcp=pIp+IPHEADLEN;,建议和结论:1.魔鬼数字是一种不好的编程习惯,一方面代码可读性差,另一方面在修改多出数字时容易造成遗漏,从而各处使用导致不一致。2.不是所有的数字都是魔鬼数字3.有明确意义的数字应该定义长宏、枚举或者常量,如申请的内存大小、函数的返回值、各种标记位等。,宏定义错误,宏定义最常见的问题就是没有使用足够的括号去保证展开的正确性。示例1:#definemul1(a,b)(a*b)#definemul2(a,b)(a)*(b)intmain()intx=0;x=mul1(1+2,5);/*x=11*/x=mul2(1+2,5);/*x=15*/,示例2:#defineadd1(a,b)(a)+(b)#defineadd2(a,b)(a)+(b)intmain()intx=0;x=add1(1,2)*5;/*x=11*/x=add2(1,2)*5;/*x=15*/,建议和结论:1.宏定义会忠实地进行展开,这个展开过程忽略运算符、优先级和函数。2.宏定义里面的算术表达式里面的各个参数需要加括号,整个表达式本身也需要加括号。,Sizeof问题,Sizeof是一个编译时处理的操作符,sizeof最常见的问题就是混淆了结构的体积和结构指针的体积。structtheNodeinta;charb20;Node;.intx=0;structNode*pstNode;x=sizeof(Node);/x=24x=sizeof(pstNode);/x=4,建议与结论:1.sizeof是编译器在编译时处理的,而不是在程序运行时处理的2.结构指针的体积与结构体的体积是两回事,在32位机上,指针一般都是32位长的(即4字节),而结构体的长度则依赖于结构体定义,structtheNodeintb5;shortc;Node;sizeof(Node)=?,另一个常见的错误是某些结构体定义没有正确使用#pragmapack,导致结构体体积的计算与理想有偏差。,#pragmapack(1)structtheNodeintb5;shortc;Node;sizeof(Node)=?,建议和结论:1.对齐有利于提高存储效率,常见的系统一般默认4字节或8字节对齐,编译时编译器将选取系统对齐和本结构中最常基础结构二者中的较小值作为该结构的实际对齐值。2.这个错误经常发生在定义报文结构是时,报文结构一般都应该按pack(1)来定义。,变量的错误,变量的类型和存储全局变量局部变量,全局变量:定义在任何函数的外部,生命周期是在整个程序的周期内。-定义时没有初始化,或者初始化为0的全局变量存放在bss段(对于bss段,操作系统在加载时会自动全部清0)-定义时初始化为非0的全局变量存放在data段,局部变量:定义在函数内,只能在所在函数内访问-静态局部变量存放在全局堆中,生命周期是在整个程序-普通局部变量存放在栈中,生命周期是在函数内,注意:不管什么变量都要注意初始化问题,变量不初始化而直接作为右值使用是一个常犯的错误。,全局变量在定义时初始化,会使app文件增大。因此,对于全局的大数组,应该尽量避免在定义是初始化,可以在程序执行的初始化阶段进行初始化。,intarray10001000=1;intmain(intargc,char*argv)return0;用VC编出来的app大小大约为4.75M,intarray10001000;intmain(intargc,char*argv)return0;用VC编出来的app大小大约为180K,建议和结论:1.尽量避免对大的全局变量在定义时进行初始化,这样可以减小app大小,节省存储空间2.无论初始化与否,全局变量总是要占用运行时的内存空间,因此要避免定义不必要的大型全局变量,普通局部变量是存放在当前任务或系统栈中,因此避免定义过大的局部变量,从而使堆栈溢出。intmain(intargc,char*argv)intarray10001000=0;return0;有什么问题?,示例:intfunc1()inta4000;func2(0);.intfunc2()intb4000;func3(0);.,错误后果:1.在嵌入式设备上,每个任务的栈大小是有限的,一般在任务创建是指定(一般是4-40k),一旦发生栈空间溢出,容易造成栈被写坏,系统死机2.变量超大等错误无法通过编译等手段发现3.一旦发生栈被写坏,则函数调用栈也已被破坏,使得问题难以定位。,建议和结论:1.编写代码时不要定义大的局部变量,如果必须要使用大的内存,则可以通过malloc从堆中申请。2.使用局部数组时,要谨防写越界。3.如果发生调用栈损坏,可以从局部变量超大和局部数组写越界这个思路开始追查,看看问题出现时,可能发生的调用栈。,示例:char*func1()chararr200;strcpy(arr,abcd);returnarr;,错误后果:1.字符串的内存赋值到栈中,这个空间在func1返回后就不再有意义2.如果func1返回后继续向arr这个地址写入内容,则会造成栈写坏,可能导致系统崩溃,建议与结论:1.局部变量一定不要超越其作用域的范围2.编码时,在函数返回指针时要特别注意,千万不要返回栈空间地址。,思考:下面程序有什么问题?,structQueueglobal_q;/*定义一个队列*/.voidospf_routing_calc()structQueueNoden;clean_queue(global_q);/*清空队列*/.EnQueue(global_q,/*计算成功返回*/,建议与结论:1.局部变量一定不要超出其作用域的范围2.要特别关注函数中异常分支的处理,看看异常分支中有没有进行必要的资源回收和回退处理。,数组和指针的错误,访问越界指针释放错误指针移位错误数组和指针的混用,访问越界,数组和内存的访问越界是一类最常发生的错误,这类错误一旦发生,经常会引起内存链损坏、调用栈写坏等严重后果常见问题和建议:1.字符缓冲区:在进行字符串操作时(strcpy、strcat等),要确保目的缓冲区的大小足够大.(包括后面的0)2.报文缓冲区:需要考虑各种报文长度,如正常报文、畸形报文、非法报文等,3.数组定义:数组大小已变化,而引用的地方没有作相应修改,引起访问越界。(一般是魔鬼数字导致修改不全,建议用宏来标识数组大小)4.数组下标:检查数组下标的合法性,防止下标过大导致数组访问越界。5.字符串的0结尾:定义字符串数组时忘记后面的0,是一种常见错误。如charstr3=abc;,指针释放错误,指针释放内存错误是非常大的一类错误,一代代的C程序员绞尽脑汁地同这些错误作斗争,在消灭错误的同时,他们也在不断创造新的错误!最简单的一类错误就是遗漏指针释放,导致内存泄漏。主要原因:1.异常处理分支、多个处理分支中遗漏内存释放和相关的资源回收处理。建议:a:关注各个分支的资源释放处理,尤其是新增加一个分支时。b:将资源释放集中处理,整合成一个流程。2.责任主体不清,接口设计没有明确释放主体a:设计定义接口时,要明确定义资源的申请者和释放者。,引用已释放的指针也是最常见的一类错误。如:free(pIntf);printf(freeinterface%s,pIntf-name);建议与结论:1.即使是刚释放的内存,也不能再访问,因为里面的内容已经没有意义了。2.养成释放内存后,立即将指针设成NULL的良好习惯。,指针赋值除了容易造成内存释放后再访问的问题外,还容易导致内存重复释放。pRoute-pIntf=pIntf;.free(pIntf);.if(NULL!=pRoute-pIntf).theID=pRoute-pIntf-theID;/*错误访问*/free(pRoute-pIntf)/*重复释放*/.,建议与结论:1.对于等价指针的情况,必须要严格明确申请者和释放者。2.在释放的时候要将所有的等价指针设成NULL,指针移位错误,指针可以通过加减运算进行推移。需要注意的是,指针的加、减运算都是基于它所指向的对象尺寸大小进行考虑的,常见的错误就是额外的计算了数据类型的体积。示例:intarr100;int*p=arr;p=p+sizeof(int);/*错误*/p=p+1;/*正确*/,建议和结论:1.指针偏移时,编译器已经考虑了对象的体积,编程者不需要再画蛇添足。2.void型指针由于编译器不知道其对象体积,所以不能进行加、减偏移运算,指针和数组的混用,指针和数组有相同之处,但是在绝大多数情况下二者含义是不同的,不可混淆。,char*b=abc;。,chara4=abc;,1.给指针赋值为数组首地址,可以通过推移指针来访问数组各元素chara4;char*p=a;通过a1和*(p+1)都可以正确访问数组2.函数使用数组作为参数时,数组地址只能以指针的方式传入,此时通过数组或指针的方式定义都是可以的。chara4;intfunc(char*p).ret=func(a);char*p=a;,流程和逻辑的错误,统计和计数的错误任务切换,统计和计数错误,统计包括很多,如报文数目统计、错误统计、各种表项统计等。统计计数的常见错误一般有以下几种情况:1.统计计数变量没有初始化。2.多个分支时,某些分支中遗漏统计。3.统计计数变量溢出,统计计数错误引起的问题大多不太严重,但有时也可能导致严重的问题。一个真实的案例:某一子系统采用一个LONG型计数器记录系统启动到当前的毫秒数,并以此进行定时器调度,不幸的是,这个计数器没有进行溢出保护,也就是说0 x7FFFFFFF/1000/3600/24=24.8天后,这个计数器将发生溢出。后果:这个设备在实验室从来没有这么长时间运行过,问题一直没有被发现;结果,一次开局,路由器在网上运行了二十多天,计数器发生溢出,定时器系统崩溃,系统重启。由于没有调用栈,重启时没有任何操作,问题很难定位;直到又过了二十多天,问题再次出现,研发人员发现间隔的时间惊人的一致,才发现了这个问题。,建议与结论:1.统计计数虽然简单,但也不可以掉以轻心,分支流程容易遗漏,需要特别关注。2.对于计数器一定要考虑什么时候会溢出,以及溢出时的保护处理。,任务切换,1.在多任务编程时,需要考虑任务切换时对全局资源的保护处理。2.如果多个任务间有严格的时序要求,则需要保证各个任务间的同步,防止乱序而导致意想不到的结果。,C语言程序设计常见问题,1.下面代码有什么问题?当我试图访问p2是得到了错误,为什么?char*p1,p2;p2=(char*)0 x80000000;,作者的原因是定义两个char型的指针,但是上述代码实际上等价于:char*p1,p2;或者char*p1;charp2;因此作者在后面的指针赋值语句时会产生错误正确的定义法是:char*p1,*p2;,2.C语言的关键字extern在函数的声明中起到什么作用?如:externintfunc(int);,如果函数的声明中带有关键字extern,除了暗示这个函数可能在其他源文件里定义外,无其他作用。下面两个函数的声明没有明显区别:externintfunc(int);intfunc(int);,3.下面两种对于定义string_t数据类型的方法,哪一种更好?typedefchar*string_t#definestring_tchar*,通常讲,typedef要比#define好,尤其在对指针的处理上。示例:typedefchar*string_t1#definestring_t2char*string_t1s1,s2;string_t2s3,s4上述变量s1、s2、s3都被定义成了char*,而s4被定义成了char,而不是预期的char*。根本原因就是#define只是进行字符串的简单替换。除此之外,宏定义有#ifdef和#ifndef等用来进行逻辑判断,这是它的长处。,4.typedef中的嵌套定义问题。下面定义有问题吗?typedefstructmystructinta;MY_STRUCTURE*pnext;MY_STRUCTURE;,规范的做法是:structmystructinta;structmystruct*pnext;typedefstructmystructMY_STRUCTURE,5.下面的方法定义数组有问题吗?constinta=5;intarraya;,这个问题讨论的是常量与只读变量的区别。常量,如5、“abc”等肯定是只读的,因为程序中根本没有地方存放它们的值,别说修改它了。而只读变量,则是在内存中开辟一个地方来存储它的值,只不过这个值不允许修改。C语言的const就是用来限定一个变量不允许修改的修饰符。上述代码的a被修饰为只读变量,但它本质上还是变量,而不是常量。C语言规定,数组的定义必须是常量,而不能是只读变量。,6.下面的代码中编译器会报一个错误,为什么?typedefchar*charptr;charstr4=abc;constchar*p1=str;constcharptrp2=str;p1+;p2+;,p2+有问题,会报错。因为constcharptrp2与#define不同,不是进行简单的字符串替换,它实际上定义的是一个常指针,即指针初始化后不允许改变。类似于constlongxyz;只不过charptr是我们自己定义的类型。,7.为什么结构体变量不能用“=”和“!=”进行比较?,C语言是一种低级语言,没有一种简单有效的办法实现结构体变量的比较。具体原因有两个:1)结构体对齐问题,导致的一些填充域,这些填充域可能是随机值,因此按字节比较;2)如果按域比较,结构体中若含有指针域,则指针域所指内容的比较则无法实现。因此结构体的比较往往需要各应用模块根据自己的需要专门写一个比较函数,8.如何初始化一个联合体的任意成员?typedefunionmyunionintx;shorty;UN;UNux=4,1;/很遗憾,标准C不支持初始化union的任意成员,而只能初始化它的第一个成员。,9.下面代码能告诉我们b在a和c之间吗?if(abc).,上述代码实际等价于if(ab)c).意思是,取(ab)判断的逻辑结果,然后再和c比大小。,10.C语言的指针很重要,也很灵活,不过它到底有哪些好处?请列举,1.方便使用动态分配的数组2.对相同类型或相似类型的多个变量进行通用访问3.变相改变函数的只传递特性,如将变量地址作为参数传入函数,这样就可以修改该变量的值4.动态扩展数据结构,如链表、hash表5.遍历数组6.节省函数调用代价,将参数尤其是大个的参数,按指针传递,以减少开销。7.,11.下面的代码打印出来的结果是多少?inti,array5,*ip;ip=array;for(i=0;i5;i+)arrayi=i;printf(rn%d,*(ip+3*sizeof(int);,呵呵。,具体打印什么我也不清楚。问题就出在ip+3*sizeof(int)上了。指针的相加实际上已经考虑了其类型的长度。示例:如果ip所指向的地址为0 x80000000,则ip+1所指的地址0 x80000004。因此本题中不需要再乘以sizeof(int)了。,12.*p+到底是给谁加1?是给指针p还是p所指的内容加1?,单目操作符和操作数的结合顺序是:从右到左一个表达式中单目运算符的执行顺序是:从左到右因此本题中实际上是对指针p加1,而不是p的内容加1,如果要对p的内容加1,则用下面的表达式:(*p)+,13.我有一个char*类型的指针,恰好指向一个int型,我想让指针跳过这个int,跳到下一个char,试问下面代码能否实现?(int*)p+;,这种实现标准C不支持,但是主流编译器默认都会支持,如VC和gcc。虽然这么用一般不会有什么问题,不过还是建议大家不要这么用,上面问题可以直接用下面代码替代:p+=sizeof(int);,14.为什么我不能对void*型的指针进行算术运算?如:void*p=array;p+=5;,前面说过,对指针进行算术运算(指针加、减运算)时实际上已经考虑了该指针所指类型的大小了。同理,如果无法知道该指针类型的大小,则无法进行指针的算术运算。因为编译器根本不知道你这个指针所指的变量占几个字节,也就无法进行指针偏移。,15.有些头文件中将NULL定义为0,为什么?NULL用于表示指针为空指针;一般用于指针变量的初始化。NULL的具体的机器表示也随机器而定,不过一般都是0.,16.在源文件里定义了一个数组:intarray5;我能否在另一个文件里声明一个指针,从而引用这个数组?externint*array;,不可以,程序运行时会告诉你非法访问!指向类型T的指针并不等价于类型T的数组。正确的用法是:externintarray;,17.有人说数组名无法赋值,但是下面程序确实可以工作,难道我记错了?intfun(charsz100).if(0=sz0)sz=NONE;.,在C语言中,数组无法真正传递给函数,因而在编译器内部这个函数就被解释为:intfun(char*sz)因此,C语言函数参数若是数组,则实际传递的是一个指针,也就是该数组的首地址。因此上述函数当然没有问题。,18.假定有一个整型数组a,则a和a和,如果需要对行进行遍历的话,就需要一个指向行的指针,定义如下:intarray23=1,2,3,4,5,6;int(*p)3=0;/int*p2=0;p=,20.有人写了一个将整数转换成字符串的函数,char*itoa(intn)charbuf20;sprintf(buf,%d,n);returnbuf;如果按下面调用,有什么问题?char*p3;p3=itoa(5);intc;c=1;printf(%s,p3);,char*itoa(intn)函数返回了栈空间的地址,要知道,栈空间的内容在该函数返回后就不再受到保护,也就是说此时函数的栈空间的已经被系统回收。如果函数返回了栈空间的内存,则很容易出现错误的结果,因为该空间可能已经分配给了其他函数或任务。,21.为什么有些代码中malloc申请内存时的返回值总是强制类型转换一下,不转行不行?char*p=(char*)malloc(100);,在C语言引入void*指针类型之前,这种强制转换主要是为了消除编译告警。在C语言引入了void*指针类型之后,这种转换已经没有必要,但是这是一种良好的编程习惯,建议大家继续保持这种强制转换,增加代码可读性。,22.下面的程序有什么问题?#definePI3.14intfit_size(doublerount)if(rount/PI=10)|(rount/PI=20)return1;return0,此题同样是浮点数比较问题。浮点数不能直接与某一个整数进行比较,而是应该和一个范围比较。上面程序应该改成:#definePI3.14intfit_size(doublerount)if(fabs(rount/PI-10)0.000001)|(fabs(rount/PI-20)0.000001)return1;return0,23.下面这样的写法正确吗?intfun(inta)intk=0;switch(a)default:k=1;break;case1:k=2;break;case2:k=3;break;return0,这种写法正确,因为switchcase里的default语句可以放在任意的位置,当然可以放在开头。但是一般不建议这样做,建议把default语句放在switch的最后,这样代码更清晰。,24.下面程序有什么问题?for(i=start;iend;i+);printf(rn%d,i);,for循环后面的“;”实际上指示了for循环的终结。因此本例中,printf语句实际上只会执行一次。这种错误经常发生,一般都是笔误,因为一行代码写完了,大家容易习惯性地在后面加一个“;”。,25.C语言支持结构体的整体赋值吗?如:structmyStrutctinta;intb;charc;myStrutctst1,st2;.st1=st2;,早期的ANSIC不支持,但是后来支持了,C语言支持结构体的整体赋值,一般是依次用DWORD去赋,效率较高。因此,大家以后编码过程中也可以这么用。,26.下面代码能工作吗?ai=i+;,这个在C语言中是未定义的,也就是依赖于各编译器的实现,不同编译器编出来的结果可能不一致。因此,大家在以后的不要写出这种代码。,27.下面代码有什么问题?char*p=iamhungry.;p0=i;,指行时可能会提示非法操作!因为字符串“iamhungry.”是存放在app或者exe文件里的,也就是程序的代码段或者只读内存区中,其中的值也是无法改变的。,28.有人说goto语句具有破坏性,建议用于不要使用,这样是不是太过分了?,goto语句建议大家平时尽量不要用,但是不是绝对的,下面情况下用goto语句就比较理想:如果要跳出最里层循环,则用goto较好。intfun(int*a)for(.)for(.)for(.)if(.)gotoout;.out:return0,29.为什么sizeof不能告诉我一个指针所指的内存有多大?,sizeof是C语言的一个关键字,因此是在程需编译的时候确定的,但是指针可以在程序执行期间任意乱指,因此每次所指的内存大小也不是固定的。所以C语言的sizeof(指针)只能返回指针的大小,而不能返回指针所指内存的大小,因为编译器根本无法知道其确切大小。,30.动态分配一个结构指针,该结构包含其他指向其他动态分配的对象,当释放这个结构的时候需要显示的释放该结构指向的其他对象吗?structlistinta;char*item;structlist*next;structlist*plist;plist=malloc(sizeof(structlist);.free(plist),需要,因为结构体里面所保存的其他的对象指针,对于free来说是透明的。free的时候系统根本不会管理你这个结构体里面的域指针所指向的对象。如果你之前没有释放这些对象,那么将会造成内存泄漏,导致这些对象永远无法释放了。,ThankYou!,
展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 图纸专区 > 课件教案


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!