在线咨询
eetop公众号 创芯大讲堂 创芯人才网
切换到宽版

EETOP 创芯网论坛 (原名:电子顶级开发网)

手机号码,快捷登录

手机号码,快捷登录

找回密码

  登录   注册  

快捷导航
搜帖子
查看: 1958|回复: 0

编写优质无错C程序秘诀!《经验谈》

[复制链接]
发表于 2007-5-24 08:22:23 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?注册

x
编写优质无错C程序秘诀!《经验谈》 这里我将陆续给大家载出我在以前学习和编写c代码时遇到的问题和解决方法、学习的心得,有些是经过查询一些曾经参加微软microsoft的开发小组的老程序员的书籍和资料后提供给大家! 首先,当发现错误时,要不断就以下两个问题追问自己的结果:1、怎样才能自动地查出这个错误?2、怎样才能避免这个错误?关于错误:错误可以分为两类:1、开发某一功能时产生的错误。2、程序员认为该功能已经开发完成之后仍然遗留在代码中的错误。第一种错误好解决,可以把编译器可以设置的警告等级开关打开,以及语法检查来排除;逻辑错误也可以使用跟踪手段来排除。跟踪逻辑错误就相对麻烦一些,要消除这些麻烦就要养成一个好的编程习惯和方法。第二种错误时非常隐蔽的,需要长期的实践和经验在其中,还要对c语言具有深刻的了解才能够提高上来,这里就是要告诉大家一些这样的事情,通过代码解说来阐明具体事实。以下的文章里,实际上有许多是微软 microsoft 的老程序员开发 word 和 excel 的经验之谈,这也是我当初学习他们的经验时的体会和材料的总结和整理。总之,这些对于在c道路上前进的人们是非常重要的,不敢独占,先拿出来以供大家享受 (第一个问题)考虑自己所用的语言和编程环境?使空语句明显化!充分利用语言的特性和编程环境,把所有环境下的调试报错等级开关都打开,注意使用语言的保留字,例如下面的两段程序对比: /*复制一个不重叠的内存块*/void *memcpy(void *pvto, void *pvfrom,size_t size){    byte *pbto = (byte *)pvto;    byte *pbfrom = (byte *)pvfrom;    while(size-- > 0);        *pbto++ = *pbfrom++;    return (pvto);}从以上缩进格式可以看出,while后的分号肯定是一个错误。但编译器认为这是一个合法的语句,允许循环体为空语句。报警开关都打开时,大多编译器都能够报出这一错误。但需要用空语句时,最好实用null(大写)明确出来:char *strcpy(char *pchto, char *pchfrom){    char *pchstart = pchto;    while(*pchto++ = *pchfrom++)        null;/*此处null大写*/    return (pchstart);}这样,编译器编译程序接受显式的null语句,把隐式空语句自动地当做错误标出。(第二个问题)无意的赋值。例如:if(ch = '\t')    expandtab();有些编译器允许在程序&&和||表达式以及if、for和while中直接使用赋值的地方禁止简单赋值,如果以上五种情况将==偶然地键入为=号,就会报错。while(*pchto++ = *pchfrom++)    null;编译程序就会产生警告信息,为了防止这种情况出现,可以这样做:while((*pchto++ = *pchfrom++) != '\0')    null;这样做的结果由两个好处:1、现在的编译器不会为这种冗余的比较产生额外的代码和开销,可以将其优化掉。2、可以少冒风险,尽管以上两种都合法,但这是更安全的用法。 (第三个问题)参数错误:例如:fprintf(stderr, "unable to open file %s.\n",filename);......fputc(stderr,'\n');这个程序看上去好像没有问题,实际上fputc的参数顺序错了。幸好ansi c提供了函数原型,在编译时自动查出这些错误。 ansi c标准要求每个库函数都必须有原型,stdio.h中可以查到:int fputc(int c, file *stream);如果在程序文件头里给出了原型,这类错误就可以检查出。ansi c虽然要求标准库函数必须有原型,但并不要求用户编写的函数也必须有原型。可以有,也可以没有。有些程序员经常抱怨对函数的原型进行维护,如果没有原型,就不得不依靠传统的测试方法来查出程序中的调用错误,大家可以扪心自问:究竟哪个更重要?利用原型可以生成质量更好的代码。ansi c标准使得编译程序可以根据原型信息进行相应的优化。有这样的名言:投资者与赌徒之间的区别在于投资者利用每一次机会,无论它是多么小,去争取利益;而赌徒则只靠运气。我们应该将这一概念同样应用于编程活动。把所有的警告开关都打开,除非有极好的理由才不这样做!(原则一)如果有单元测试,就进行单元测试。你认识那个程序员宁愿花费时间去跟踪排错,而不是编写新的代码?肯定有这样的程序员,但我至今还没有见到一个。当你写程序时,要在心中时刻牢记着假想编译程序这一概念,这样就可以毫不费力或者直费很少力气利用每个机会抓住错误。如果想要快速容易地发现错误,就要利用工具的相应特性对错误进行定位。错误定位的越早,就能够越早地投身于更有趣的工作。努力减少程序员查错的技巧。可以选择编译程序的环境来实现。高级的编码方法虽然可以查出或减少错误,但它们也要求程序要有较多的技巧,因为程序员必须学习这些高级的编码方法。 (原则二)自己设计并使用断言。    利用编译器自动查错固然好,但实际上只是很少一部分。如果排除掉了程序中的所有错误,大部分时间程序会正确工作。 看一下下列代码:strcopy = memcpy(malloc(length),str,length);    该语句在多数情况下会工作的很好,除非malloc的调用产生失败。一旦产生,就会给memcpy返回一个null指针,而memcpy处理不了null指针,这样的错误产生,如果在交付用户之前将导致程序的瘫痪。但如果交付了用户,那用户就一定“走运”了。解决方法:对null指针进行检查,如果为null,就给出一条错误信息,并终止memcpy执行。ee/*拷贝不重叠的内存块*/void memcpy(void *pvto, void *pvfrom, size_t size){    void *pbto = (byte *)pvto;    void *pbfrom = (byte *)pvfrom;    if(pvto == null || pvfrom == null)    {        fprintf(stderr, "bad args in memcpy!\n");        abort();    }     while(size-- > 0)        *pbto++ = *pbfrom++;    return(pvto);}    只要调用时错用了null指针,这个函数就会查出来。但测试的代码增加了一倍,降低了执行速度,这样“越治病越糟”,还有没有更好的方法?    有,利用c的预处理程序!    这样就会保存两个版本。一个整洁快速,用于交付用户;另一个臃肿缓慢(包含了额外的检查),用于调试。这样就要同时维护同一个程序的两个版本,利用c的预处理程序有条件地包含相应的部分。例如:只有定义了debug时,才对应null指针测试。void memcpy(void *pvto, void *pvfrom, size_t size){    void *pbto = (byte *)pvto;    void *pbfrom = (byte *)pvfrom;    #ifdef debug        if(pvto == null || pvfrom == null)        {            fprintf(stderr, "bad args in memcpy!\n");            abort();        }     #endif    while(size-- > 0)        *pbto++ = *pbfrom++;    return(pvto);}这样,调试编译时开放debug,进行测试程序和找错;交付用户时,关闭debug后进行编译,封装之后交给经销商。    这种方法的关键是保证调试代码不在最终产品中出现。那么还有没有比以上两种更好的方法,有!下次再讲。 (准则二续)利用断言进行补救。     实际上,memcpy中的调试代码编的非常蹩脚,喧宾夺
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

站长推荐 上一条 /2 下一条

小黑屋| 手机版| 关于我们| 联系我们| 隐私声明| EETOP 创芯网
( 京ICP备:10050787号 京公网安备:11010502037710 )

GMT+8, 2025-3-4 16:26 , Processed in 0.369634 second(s), 10 queries , Gzip On, Redis On.

eetop公众号 创芯大讲堂 创芯人才网
快速回复 返回顶部 返回列表