1、C 程序设计授课教师授课教师 孙向群孙向群fd_C语言编程常见错误分析语言编程常见错误分析专业名称:物流工程vC语言是目前世界上最通用的编程语言之一,也是目前研发使用最多的编程语言。v同各种各样的bug作斗争,是每一个C程序员每天所面临的课题。v本文将从微观角度出发,对一些常见出错类型的案例进行分析,希望大家今后能避免类似的错误。程序设计(程序设计(Programming in C)v数字和表达式错误v变量的错误v数组和指针的错误v逻辑和流程的错误程序设计(程序设计(Programming in C)数字和表达式的错误数字和表达式的错误v运算符和优先级的错误v字节序的错误v魔鬼数字v宏定义的错
2、误vsizeof的错误程序设计(程序设计(Programming in C)最常见的运算符错误就是最常见的运算符错误就是“=”和和“=”的误用的误用vint main()vvint ret;vret=GetVars();vif(ret=VOS_OK)vv.vvreturn 0;vv错误后果:v1.变量被错误赋值。v2.逻辑判断不正确。v建议和结论:v尽管是初级bug但是还是常有发生,建议写成“VOS_OK=ret”的形式,这样在编译的时候即可发现这种错误。程序设计(程序设计(Programming in C)“+”和和“-”在表达式中的应用在表达式中的应用v#define mypower(a)
3、(a)*(a)v int main()v vint i=1,j=2;vj=mypower(+i);vprintf(rn%i=d j=%d,i,j);vreturn 0;v v 错误后果:v 结果与期望的不一致v 建议与结论:v 1.对于“+”和“-”这种基本的知识还是应该掌握的v 2.自增和自减变量在本表达式中不要再引用,否则可能依赖编译器实现v 3.没有把握的用法千万不要用,否则可能导致意想不到的错误程序设计(程序设计(Programming in C)优先级问题也是编码初期容易出现的问题优先级问题也是编码初期容易出现的问题vC语言有众多的运算符号,它们之间的优先级关系非常复杂,即使是一个熟
4、练的C程序员,要清楚地记住这些优先级关系也绝非易事。vif(high if(high if(a|b)&(a&c)vif(a|b=1)=if(a|b)=1)程序设计(程序设计(Programming in C)v建议和结论:v不要使用默认优先级,使用括号来保证自己的运算优先级,不要考验字节的记忆力。程序设计(程序设计(Programming in C)字节序错误字节序错误v网络设备和网络协议的开发涉及到许多字节序问题。v网络序:v所有设备、系统都一样,表示字节在网络中的传输顺序,也就是设备接收、发送数据的顺序。数据总是按照从“高字节”=“低字节”的顺序发。程序设计(程序设计(Programmin
5、g in C)v主机序:v依赖于CPU,表示的是字节在内存中的存放顺序。v对于Intel系列CPU:高字节存放高位,低字节存放低位;和网络序相反;一般称为“小尾”或者“小端”(little endian)v对于PPC系列CPU:高字节存放低位,低字节存放高位;和网络序相同;一般称为“大尾”或者“打端”(big endian)程序设计(程序设计(Programming in C)v对于32位int型数0 x12345678,intel和ppc系列CPU的存放格式分别如下:程序设计(程序设计(Programming in C)v假定有一种协议报文,报文类型为2个字节,某一特定类型为0 xABCD程
6、序设计(程序设计(Programming in C)两个因为字节序错误导致的真实案例两个因为字节序错误导致的真实案例v案例一:v某一种协议,其中一种报文类型是3,但是在代码中填写报文类型时没有进行字节序转换,导致对端总是识别报文类型为0 x0300,于是对端认为是非法报文,直接作丢弃处理v后果:当时使用的是Intel的CPU,这种错误类型的报文从来没有被处理过,当时也没有做单元测试,错误很久以后才被发现。因为这个报文是用来进行协议性能优化的,修改过后协议性能得到极大提升。程序设计(程序设计(Programming in C)v案例二:v 某一种协议,报文中有一个字段用DWORD表示报文长度,但
7、是在填写报文时遗漏了字节序转换,因为使用的是Intel的CPU,导致对端从收到的报文中提取出的报文长度为类似0 x4800000000的巨大数字。我们字节的设备在接收时也遗漏了字节序转换,所以从收到的报文中反而能正确提取出报文长度,该设备也一直没有与cisco的设备做互通测试,使该问题一直没有暴露出来。v 后果:该设备第一次开局时,与cisco设备互通,结果周边所有cisco的设备异常重启(cisco当年设备的鲁棒性也比较差劲!)。程序设计(程序设计(Programming in C)字节序错误不只在报文中存在字节序错误不只在报文中存在v 一.联合域定义:v union v vULONG ul
8、IP;vUCHAR szIP4;v stIPAddr;v 如果赋值:v stIPAddr.szIP0=192;v stIPAddr.szIP1=168;v stIPAddr.szIP2=0;v stIPAddr.szIP3=1;v 则大端和小端上stIPAddr.ulIP的值不同:v 大端:stIPAddr.ulIP=0 xC0A80001;v 小端:stIPAddr.ulIP=0 x0100A8C0;程序设计(程序设计(Programming in C)v 二.指针强转:v 定义如下变量:v ULONG ulTool=0 x12345678;v USHORT*pusTool=(USHORT*
9、)&ulTool;v UCHAR*pucTool=(UCHAR*)&ulTool;v 则在大端和小端上,*pusTool 和*pucTool 的值是不同的。v 大端系统:v*pusTool=0 x1234v*pucTool=0 x12v 小端系统:v*pusTool=0 x5678v*pucTool=0 x78程序设计(程序设计(Programming in C)v建议和结论:v1.填写报文和解析报文时一定要注意字节序问题,使用相应的宏操作(htonl、ntohl等)v2.当取变量的一部分值时,如联合体、强制指针转换等,需要考虑字节序问题v3.互通测试很重要程序设计(程序设计(Programm
10、ing in C)魔鬼数字问题魔鬼数字问题v魔鬼数字是指直接使用数字,而不是使用预先定义好的宏、常量、枚举等,这是一种不好的编程习惯。如下:vl=round/3.14159;vDeadInt=HelloInt*4;vpstPack=malloc(36);vpTcp=pIp+20;程序设计(程序设计(Programming in C)vconst double PI=3.14159;vl=round/PI;v#define DEADTIME 4vDeadInt=HelloInt*DEADTIME;v#define PACKSIZE 36vpstPack=malloc(PACKSIZE);v#de
11、fine IPHEADLEN 20vpTcp=pIp+IPHEADLEN;程序设计(程序设计(Programming in C)v建议和结论:v1.魔鬼数字是一种不好的编程习惯,一方面代码可读性差,另一方面在修改多出数字时容易造成遗漏,从而各处使用导致不一致。v2.不是所有的数字都是魔鬼数字v3.有明确意义的数字应该定义长宏、枚举或者常量,如申请的内存大小、函数的返回值、各种标记位等。程序设计(程序设计(Programming in C)宏定义错误宏定义错误v宏定义最常见的问题就是没有使用足够的括号去保证展开的正确性。v示例1:v#define mul1(a,b)(a*b)v#define m
12、ul2(a,b)(a)*(b)vint main()vvint x=0;vx=mul1(1+2,5);/*x=11*/vx=mul2(1+2,5);/*x=15*/v 程序设计(程序设计(Programming in C)v示例2:v#define add1(a,b)(a)+(b)v#define add2(a,b)(a)+(b)vint main()vvint x=0;vx=add1(1+2)*5;/*x=11*/vx=add2(1+2)*5;/*x=15*/v 程序设计(程序设计(Programming in C)v建议和结论:v1.宏定义会忠实地进行展开,这个展开过程忽略运算符、优先级和
13、函数。v2.宏定义里面的算术表达式里面的各个参数需要加括号,整个表达式本身也需要加括号。程序设计(程序设计(Programming in C)Sizeof问题问题v Sizeof是一个编译时处理的操作符,sizeof最常见的问题就是混淆了结构的体积和结构指针的体积。v struct theNodev vint a;vchar b20;v Node;v.v int x=0;v struct Node*pstNode;v x=sizeof(Node);/x=24v x=sizeof(pstNode);/x=4程序设计(程序设计(Programming in C)v建议与结论:v1.sizeof是编
14、译器在编译时处理的,而不是在程序运行时处理的v2.结构指针的体积与结构体的体积是两回事,在32位机上,指针一般都是32位长的(即4字节),而结构体的长度则依赖于结构体定义程序设计(程序设计(Programming in C)vstruct theNode vvint b5;vshort c;vNode;vsizeof(Node)=?v另一个常见的错误是某些结构体定义没有正确使用#pragma pack,导致结构体体积的计算与理想有偏差。v#pragma pack(1)vstruct theNode vvint b5;vshort c;vNode;vsizeof(Node)=?程序设计(程序设计
15、(Programming in C)v建议和结论:v1.对齐有利于提高存储效率,常见的系统一般默认4字节或8字节对齐,编译时编译器将选取系统对齐和本结构中最常基础结构二者中的较小值作为该结构的实际对齐值。v2.这个错误经常发生在定义报文结构是时,报文结构一般都应该按pack(1)来定义。程序设计(程序设计(Programming in C)变量的错误变量的错误v变量的类型和存储v全局变量v局部变量程序设计(程序设计(Programming in C)v全局变量:定义在任何函数的外部,生命周期是在整个程序的周期内。v-定义时没有初始化,或者初始化为0的全局变量存放在bss段(对于bss段,操作系
16、统在加载时会自动全部清0)v-定义时初始化为非0的全局变量存放在data段程序设计(程序设计(Programming in C)v局部变量:定义在函数内,只能在所在函数内访问v-静态局部变量存放在全局堆中,生命周期是在整个程序v-普通局部变量存放在栈中,生命周期是在函数内程序设计(程序设计(Programming in C)v注意:v不管什么变量都要注意初始化问题,变量不初始化而直接作为右值使用是一个常犯的错误。程序设计(程序设计(Programming in C)v全局变量在定义是初始化,会使app文件增大。因此,对于全局的大数组,应该尽量避免在定义是初始化,可以在程序执行的初始化阶段进行初
17、始化。v int array10001000=1;v int main(int argc,char*argv)v vreturn 0;v v 用VC编出来的app大小大约为180Kv int array10001000;v int main(int argc,char*argv)v vreturn 0;v v 用VC编出来的app大小大约为4.75M程序设计(程序设计(Programming in C)v建议和结论:v1.尽量避免对大的全局变量在定义时进行初始化,这样可以减小app大小,节省存储空间v2.无论初始化与否,全局变量总是要占用运行时的内存空间,因此要避免定义不必要的大型全局变量程序
18、设计(程序设计(Programming in C)v普遍局部变量是存放在当前任务或系统栈中,因此避免定义过大的局部变量,从而使堆栈溢出。vint main(int argc,char*argv)vvint array10001000=0;vreturn 0;vv有什么问题?程序设计(程序设计(Programming in C)v 示例:v int func1()v vint a4000;vfunc2(0);v.vv v int func1()v vint b4000;vfunc3(0);v.v v 错误后果:v 1.在嵌入式设备上,每个任务的栈大小是有限的,一般在任务创建是指定(一般是4-40
19、k),一旦发生栈空间溢出,容易造成栈被写坏,系统死机v 2.变量超大等错误无法通过编译等手段发现v 3.一旦发生栈被写坏,则函数调用栈也已被破坏,使得问题难以定位。程序设计(程序设计(Programming in C)v建议和结论:v1.编写代码时不要定义大的局部变量,如果必须要使用大的内存,则可以通过malloc从堆中申请。v2.使用局部数组时,要谨防写越界。v3.如果发生调用栈损坏,可以从局部变量超大和局部数组写越界这个思路开始追查,看看问题出现时,可能发生的调用栈。程序设计(程序设计(Programming in C)v示例:vchar*func1()vvchar arr200;vstr
20、cpy(arr,abcd);vreturn arr;vv错误后果:v1.字符串的内存赋值到栈中,这个空间在func1返回后就不再有意义v2.如果func1返回后继续向arr这个地址写入内容,则会造成栈写坏,可能导致系统崩溃程序设计(程序设计(Programming in C)v建议与结论:v1.局部变量一定不要超越其作用域的范围v2.编码时,在函数返回指针时要特别注意,千万不要返回栈空间地址。程序设计(程序设计(Programming in C)思考:下面程序有什么问题?思考:下面程序有什么问题?v struct Queue global_q;/*定义一个队列*/v.v void ospf_r
21、outing_calc()v vstruct QueueNode n;vclean_queue(global_q);/*清空队列*/v.vEnQueue(global_q,n);/*入队列*/v.vif(err)/*出错返回*/vvreturn vvclean_queue(global_q);/*清空队列*/vreturn;/*计算成功返回*/v 程序设计(程序设计(Programming in C)v建议与结论:v1.局部变量一定不要超出其作用域的范围v2.要特别关注函数中异常分支的处理,看看异常分支中有没有进行必要的资源回收和回退处理。程序设计(程序设计(Programming in C)
22、数组和指针的错误数组和指针的错误v访问越界v指针释放错误v指针移位错误v数组和指针的混用程序设计(程序设计(Programming in C)访问越界访问越界v数组和内存的访问越界是一类最常发生的错误,这类错误一旦发生,经常会引起内存链损坏、调用栈写坏等严重后果v常见问题和建议:v1.字符缓冲区:在进行字符串操作时(strcpy、strcat等),要确保目的缓冲区的大小足够大.(包括后面的0)v2.报文缓冲区:需要考虑各种报文长度,如正常报文、畸形报文、非法报文等程序设计(程序设计(Programming in C)v3.数组定义:数组大小已变化,而引用的地方没有作相应修改,引起访问越界。(一
23、般是魔鬼数字导致修改不全,建议用宏来标识数组大小)v4.数组下标:检查数组下标的合法性,防止下标过大导致数组访问越界。v5.字符串的0结尾:定义字符串数组时忘记后面的0,是一种常见错误。如char str3=abc;程序设计(程序设计(Programming in C)指针释放错误指针释放错误v 指针释放内存错误是非常大的一类错误,一代代的C程序员绞尽脑汁地同这些错误作斗争,在消灭错误的同时,他们也在不断创造新的错误!v 最简单的一类错误就是遗漏指针释放,导致内存泄漏。v 主要原因:v 1.异常处理分支、多个处理分支中遗漏内存释放和相关的资源回收处理。v 建议:v a:关注各个分支的资源释放处
24、理,尤其是新增加一个分支时。v b:将资源释放集中处理,整合成一个流程。v 2.责任主体不清,接口设计没有明确释放主体v a:设计定义接口时,要明确定义资源的申请者和释放者。程序设计(程序设计(Programming in C)v引用已释放的指针也是最常见的一类错误。如:vfree(pIntf);vprintf(free interface%s,pIntf-name);v建议与结论:v1.即使是刚释放的内存,也不能再访问,因为里面的内容已经没有意义了。v2.养成释放内存后,立即将指针设成NULL的良好习惯。程序设计(程序设计(Programming in C)v 指针赋值除了容易造成内存释放后
25、再访问的问题外,还容易导致内存重复释放。v pRoute-pIntf=pIntf;v.v free(pIntf);v.v if(NULL!=pRoute-pIntf)v v.vtheID=pRoute-pIntf-theID;/*错误访问*/vfree(pRoute-pIntf)/*重复释放*/v.v 程序设计(程序设计(Programming in C)v建议与结论:v1.对于等价指针的情况,必须要严格明确申请者和释放者。v2.在释放的时候要将所有的等价指针设成NULL程序设计(程序设计(Programming in C)指针移位错误指针移位错误v指针可以通过加减运算进行推移。v需要注意的是
26、,指针的加、减运算都是基于它所指向的对象尺寸大小进行考虑的,常见的错误就是额外的计算了数据类型的体积。v示例:vint arr100;vint*p=arr;vp=p+sizeof(int);/*错误*/vp=p+1;/*正确*/程序设计(程序设计(Programming in C)v建议和结论:v1.指针偏移时,编译器已经考虑了对象的体积,编程者不需要再画蛇添足。v2.void型指针由于编译器不知道其对象体积,所以不能进行加、减偏移运算程序设计(程序设计(Programming in C)指针和数组的混用指针和数组的混用v指针和数组有相同之处,但是在绝大多数情况下二者含义是不同的,不可混淆。v
27、char*b=abc;。vchar a4=abc;程序设计(程序设计(Programming in C)v 1.给指针赋值为数组首地址,可以通过推移指针来访问数组各元素v char a4;v char*p=a;v 通过a1和*(p+1)都可以正确访问数组v 2.函数使用数组作为参数时,数组地址只能以指针的方式传入,此时通过数组或指针的方式定义都是可以的。v char a4;v int func(char*p)v v.v v.v ret=func(a);v char*p=a;程序设计(程序设计(Programming in C)流程和逻辑的错误流程和逻辑的错误v统计和计数的错误v任务切换程序设计
28、(程序设计(Programming in C)统计和计数错误统计和计数错误v统计包括很多,如报文数目统计、错误统计、各种表项统计等。v统计计数的常见错误一般有以下几种情况:v1.统计计数变量没有初始化。v2.多个分支时,某些分支中遗漏统计。v3.统计计数变量溢出程序设计(程序设计(Programming in C)v 统计计数错误引起的问题大多不太严重,但有时也可能导致严重的问题。v 一个真实的案例:v 某一子系统采用一个LONG型计数器记录系统启动到当前的毫秒数,并以此进行定时器调度,不幸的是,这个计数器没有进行溢出保护,也就是说0 x7FFFFFFF/1000/3600/24=24.8天后
29、,这个计数器将发生溢出。v 后果:v 这个设备在实验室从来没有这么长时间运行过,问题一直没有被发现;结果,一次开局,路由器在网上运行了二十多天,计数器发生溢出,定时器系统崩溃,系统重启。由于没有调用栈,重启时没有任何操作,问题很难定位;直到又过了二十多天,问题再次出现,研发人员发现间隔的时间惊人的一致,才发现了这个问题。程序设计(程序设计(Programming in C)v建议与结论:v1.统计计数虽然简单,但也不可以掉以轻心,分支流程容易遗漏,需要特别关注。v2.对于计数器一定要考虑什么时候会溢出,以及溢出时的保护处理。程序设计(程序设计(Programming in C)任务切换任务切换
30、v1.在多任务编程时,需要考虑任务切换时对全局资源的保护处理。v2.如果多个任务间有严格的时序要求,则需要保证各个任务间的同步,防止乱序而导致意想不到的结果。程序设计(程序设计(Programming in C)vC语言程序设计常见问题语言程序设计常见问题程序设计(程序设计(Programming in C)v1.下面代码有什么问题?当我试图访问p2是得到了错误,为什么?vchar*p1,p2;vp2=(char*)0 x80000000;程序设计(程序设计(Programming in C)v作者的原因是定义两个char型的指针,但是上述代码实际上等价于:vchar*p1,p2;v或者vch
31、ar*p1;vchar p2;v因此作者在后面的指针赋值语句时会产生错误v正确的定义法是:vchar*p1,*p2;程序设计(程序设计(Programming in C)v2.C语言的关键字extern在函数的声明中起到什么作用?如:extern int func(int);v如果函数的声明中带有关键字extern,除了暗示这个函数可能在其他源文件里定义外,无其他作用。下面两个函数的声明没有明显区别:vextern int func(int);vint func(int);程序设计(程序设计(Programming in C)v3.下面两种对于定义string_t数据类型的方法,哪一种更好?v
32、typedef char*string_tv#define string_t char*程序设计(程序设计(Programming in C)v 通常讲,typedef要比#define好,尤其在对指针的处理上。v 示例:v typedef char*string_t1v#define string_t2 char*v string_t1 s1,s2;v string_t2 s3,s4v 上述变量s1、s2、s3都被定义成了char*,而s4被定义成了char,而不是预期的char*。根本原因就是#define只是进行字符串的简单替换。v 除此之外,宏定义有#ifdef和#ifndef等用来进
33、行逻辑判断,这是它的长处。程序设计(程序设计(Programming in C)v4.typedef中的嵌套定义问题。v下面定义有问题吗?vtypedef struct mystructvvint a;vMY_STRUCTURE*pnext;vMY_STRUCTURE;程序设计(程序设计(Programming in C)v规范的做法是:vtypedef struct mystructvvint a;vstruct mystruct*pnext;v;vtypedef struct mystruct MY_STRUCTURE程序设计(程序设计(Programming in C)v5.下面的方法定
34、义数组有问题吗?vconst int a=5;vint arraya;程序设计(程序设计(Programming in C)v这个问题讨论的是常量与只读变量的区别。常量,如5、“abc”等肯定是只读的,因为程序中根本没有地方存放它们的值,别说修改它了。而只读变量,则是在内存中开辟一个地方来存储它的值,只不过这个值不允许修改。C语言的const就是用来限定一个变量不允许修改的修饰符。上述代码的a被修饰为只读变量,但它本质上还是变量,而不是常量。C语言规定,数组的定义必须是常量,而不能是只读变量。程序设计(程序设计(Programming in C)v6.下面的代码中编译器会报一个错误,为什么?v
35、typedef char*charptr;vchar str4=abc;vconst char*p1=str;vconst charptr p2=str;vp1+;vp2+;程序设计(程序设计(Programming in C)vp2+有问题,会报错。因为const charptr p2与#define不同,不是进行简单的字符串替换,它实际上定义的是一个常指针,即指针初始化后不允许改变。v类似于const long xyz;v只不过charptr是我们自己定义的类型。程序设计(程序设计(Programming in C)v7.为什么结构体变量不能用“=”和“!=”进行比较?vC语言是一种低级语
36、言,没有一种简单有效的办法实现结构体变量的比较。具体原因有两个:1)结构体对齐问题,导致的一些填充域,这些填充域可能是随机值,因此按字节比较;2)如果按域比较,结构体中若含有指针域,则指针域所指内容的比较则无法实现。v因此结构体的比较往往需要各应用模块根据自己的需要专门写一个比较函数程序设计(程序设计(Programming in C)v8.如何初始化一个联合体的任意成员?vtypedef union myunionvvint x;vshort y;vUN;vUN ux=4,1;/v很遗憾,标准C不支持初始化union的任意成员,而只能初始化它的第一个成员。程序设计(程序设计(Programm
37、ing in C)v9.下面代码能告诉我们b在a和c之间吗?vif(a b c)vv.v程序设计(程序设计(Programming in C)v上述代码实际等价于vif(a b)c)vv.vv意思是,取(a b)判断的逻辑结果,然后再和c比大小。程序设计(程序设计(Programming in C)v10.C语言的指针很重要,也很灵活,不过它到底有哪些好处?请列举程序设计(程序设计(Programming in C)v1.方便使用动态分配的数组v2.对相同类型或相似类型的多个变量进行通用访问v3.变相改变函数的只传递特性,如将变量地址作为参数传入函数,这样就可以修改该变量的值v4.动态扩展数据
38、结构,如链表、hash表v5.遍历数组v6.节省函数调用代价,将参数尤其是大个的参数,按指针传递,以减少开销。v7.程序设计(程序设计(Programming in C)v11.下面的代码打印出来的结果是多少?vint i,array5,*ip;vip=array;vfor(i=0;i5;i+)vvarrayi=i;vvprintf(rn%d,*(ip+3*sizeof(int);程序设计(程序设计(Programming in C)v呵呵。,具体打印什么我也不清楚。问题就出在ip+3*sizeof(int)上了。指针的相加实际上已经考虑了其类型的长度。v示例:v如果ip所指向的地址为0 x8
39、0000000,则ip+1所指的地址0 x80000004。v因此本题中不需要再乘以sizeof(int)了。程序设计(程序设计(Programming in C)v12.*p+到底是给谁加1?是给指针p还是p所指的内容加1?程序设计(程序设计(Programming in C)v单目操作符和操作的结合顺序是:从右到左v一个表达式中单目运算符的执行顺序是:从左到右v因此本题中实际上是对指针p加1,而不是p的内容加1,如果要对p的内容加1,则用下面的表达式:v (*p)+程序设计(程序设计(Programming in C)v13.我有一个char*类型的指针,恰好指向一个int型,我想让指针跳
40、过这个int,跳到下一个char,试问下面代码能否实现?v (int*)p+;程序设计(程序设计(Programming in C)v这种实现标准C不支持,但是主流编译器默认都会支持,如VC和gcc。虽然这么用一般不会有什么问题,不过还是建议大家不要这么用,上面问题可以直接用下面代码替代:v p+=sizeof(int);程序设计(程序设计(Programming in C)v14.为什么我不能对void*型的指针进行算术运算?v如:vvoid*p=array;vp+=5;程序设计(程序设计(Programming in C)v前面说过,对指针进行算术运算(指针加、减运算)时实际上已经考虑了该
41、指针所指类型的大小了。同理,如果无法知道该指针类型的大小,则无法进行指针的算术运算。因为编译器根本不知道你这个指针所指的变量占几个字节,也就无法进行指针偏移。程序设计(程序设计(Programming in C)v15.有些头文件中将NULL定义为0,为什么?vNULL用于表示指针为空指针;一般用于指针变量的初始化。vNULL的具体的机器表示也随机器而定,不过一般都是0.程序设计(程序设计(Programming in C)v16.在源文件里定义了一个数组:vint array5;v我能否在另一个文件里声明一个指针,从而引用这个数组?vextern int*array;程序设计(程序设计(Pr
42、ogramming in C)v不可以,程序运行时会告诉你非法访问!指向类型T的指针并不等价于类型T的数组。v正确的用法是:v extern int array;程序设计(程序设计(Programming in C)v17.有人说数组名无法赋值,但是下面程序确实可以工作,难道我记错了?vint fun(char sz100)vv.vif(0=sz0)vvsz=NONE;vv.v程序设计(程序设计(Programming in C)v在C语言中,数组无法真正传递给函数,因而在编译器内部这个函数就被解释为:vint fun(char*sz)v因此,C语言函数参数若是数组,则实际传递的是一个指针,也
43、就是该数组的首地址。v因此上述函数当然没有问题。程序设计(程序设计(Programming in C)v18.假定有一个整型数组a,则a和&a有什么区别?vint a2;va 和&a有什么区别?程序设计(程序设计(Programming in C)va是指向数组第一个元素的指针,而&a则是指向整个数组的指针。vint x,a2;vint*p=NULL;vp=a;vx=*p;vp=&a;程序设计(程序设计(Programming in C)v19.什么时候需要定义指向数组的指针而不是数组元素的指针?如何定义?vint array23=1,2,3,4,5,6;程序设计(程序设计(Programmi
44、ng in C)v如果需要对行进行遍历的话,就需要一个指向行的指针,定义如下:vint array23=1,2,3,4,5,6;vint(*p)3=0;v/int*p2=0;vp=&array1;v/p2=array1;程序设计(程序设计(Programming in C)v 20.有人写了一个将整数转换成字符串的函数,v char*itoa(int n)v vchar buf20;vsprintf(buf,%d,n);vreturn buf;v v 如果按下面调用,有什么问题?vchar*p3;vp3=itoa(5);vvint c;vc=1;vvprintf(%s,p3);程序设计(程序设
45、计(Programming in C)vchar*itoa(int n)函数返回了栈空间的地址,要知道,栈空间的内容在该函数返回后就不再受到保护,也就是说此时函数的栈空间的已经被系统回收。v如果函数返回了栈空间的内存,则很容易出现错误的结果,因为该空间可能已经分配给了其他函数或任务。程序设计(程序设计(Programming in C)v21.为什么有些代码中malloc申请内存时的返回值总是强制类型转换一下,不转行不行?vchar*p=(char*)malloc(100);程序设计(程序设计(Programming in C)v在C语言引入void*指针类型之前,这种强制转换主要是为了消除编
46、译告警。在C语言引入了void*指针类型之后,这种转换已经没有必要,但是这是一种良好的编程习惯,建议大家继续保持这种强制转换,增加代码可读性。程序设计(程序设计(Programming in C)v22.下面的程序有什么问题?v#define PI 3.14vint fit_size(double rount)vvif(rount/PI=10)|(rount/PI=20)vvreturn 1;vvreturn 0v程序设计(程序设计(Programming in C)v 此题同样是浮点数比较问题。浮点数不能直接与某一个整数进行比较,而是应该和一个范围比较。上面程序应该改成:v#define P
47、I 3.14v int fit_size(double rount)v vif(fabs(rount/PI-10)0.000001)|(fabs(rount/PI-20)0.000001)vvreturn 1;vvreturn 0v 程序设计(程序设计(Programming in C)v 23.下面这样的写法正确吗?v int fun(int a)v vint k=0;vswitch(a)vvdefault:vk=1;vbreak;vcase 1:vk=2;vbreak;vcase 2:vk=3;vbreak;vvreturn 0v 程序设计(程序设计(Programming in C)v这
48、种写法正确,因为switchcase里的default语句可以放在任意的位置,当然可以放在开头。v但是一般不建议这样做,建议把default语句放在switch的最后,这样代码更清晰。程序设计(程序设计(Programming in C)v24.下面程序有什么问题?vfor(i=start;iend,i+);vvprintf(rn%d,i);v程序设计(程序设计(Programming in C)vfor循环后面的“;”实际上指示了for循环的终结。v因此本例中,printf语句实际上只会执行一次。这种错误经常发生,一般都是笔误,因为一行代码写完了,大家容易习惯性地在后面加一个“;”。程序设计
49、(程序设计(Programming in C)v25.C语言支持结构体的整体赋值吗?v如:vstruct myStrutctvvint a;vint b;vchar c;vvmyStrutct st1,st2;v.vst1=st2;程序设计(程序设计(Programming in C)v早期的ANSI C不支持,但是后来支持了,C语言支持结构体的整体赋值,一般是依次用DWORD去赋,效率较高。v因此,大家以后编码过程中也可以这么用。程序设计(程序设计(Programming in C)v26.下面代码能工作吗?v ai=i+;程序设计(程序设计(Programming in C)v这个在C语言
50、中是未定义的,也就是依赖于各编译器的实现,不同编译器编出来的结果可能不一致。v因此,大家在以后的不要写出这种代码。程序设计(程序设计(Programming in C)v27.下面代码有什么问题?vchar*p=i am hungry.;vp0=i;程序设计(程序设计(Programming in C)v指行时可能会提示非法操作!因为字符串“i am hungry.”是存放在app或者exe文件里的,也就是程序的代码段或者只读内存区中,其中的值也是无法改变的。程序设计(程序设计(Programming in C)v28.有人说goto语句具有破坏性,建议用于不要使用,这样是不是太过分了?程序设