关闭

C语言错误处理策略

390人阅读 评论(0) 收藏 举报
分类:
今天写程序时,发现自己平时不爱做异常处理。(C语言的异常处理比起java的来总觉得好费劲啊~)
  上网google了一下看见了一位大牛的解释,好厉害,膜拜一下
  
1. 返回值方式:用函数的返回值标志函数是否执行成功。比如成功返回1,失败返回0。这种方式的好处是简单方便,而且不影响效率,保持了c语言的高效率。但是仍然有问题,一个问题是代码可读性的问题,如果每个函数都有这样的返回值的话,为了保持程序的正确运行,我们必须对每个函数进行正确性验证,就是在调用函数的时候检查他的返回值,这样程序代码很大一部分就可能花费在错误处理上。第二个问题就是函数的返回值冲突的问题。假设strlen函数也可能会出错,使用这种错误处理策略他的返回值应该标志它是否执行成功,但是函数计算的字符串的长度值如何自然地传递出来?最后一个问题可能是最重要的:它不强制你处理错误,而且在不进行处理的情况下,程序仍然能够运行,但结果是不可预知的。
 
返回值可以判定正确错误,或则增加一些简单的错误类型判定. 其缺点在于一旦函数修改后返回值回有变化的话就很麻烦,当然最重要的还在于无法明确获知错误信息;当然优势很明显,那就是简单.
总的来说一般返回值只是做简单的正确错误判定,而不能作为获知具体错误的手段.
这里还涉及一个使用习惯的问题,一般来说返回0位正确,非0为错误,但是也有颠倒的,这里和个人编码习惯有关,所以要特别注意就是了.
 
 
2. 全局errno方式:就是在出现错误的时候,将错误代码记录到一个全局变量errno中。比如waitpid()函数在被信号中断的情况下,将errno设置为EINTR(一宏定义常量)。这种方式解决了返回值方式遇到的返回值冲突问题,而且效率方面也是非常令人愉悦的。但是它要求用户在调用函数后检查errno的值,这种保证是脆弱的,程序仍然有可能在不处理那些errno的情况下”安然”地运行,导致未定义的结果。另一个问题出在多线程方面,errno不是线程安全的,多个线程操作同一个errno会造成混乱。
 
在windows下errno是线程安全的,不知道linux下是线程安全还是进程安全.
在ANSI C中有定义一些基本的errno,并且操作系统也会扩展一部分,但是依然无法改变其对错误描述的匮乏.
相对windows下GetLastError组合FormatMessage的组合则显得丰满很多,可以详细了解错误信息.
作为一种补充的方式,这种全局错误代码的方式也是相当不错的,和第1种方法搭配处理可以解决90%以上的错误处理.
这里特别要推荐在windows下开发,处理错误应该尽量使用GetLastError组合FormatMessage,而非errno.
 
 
3. 错误封装:就是将每个有错误返回值的函数分别用一个函数包起来,比如waitpid()函数可以封装成Waitpid()(首字母大写),在这个函数中处理相应的错误。这种错误处理方法可以很好的解决很多问题,应该说效果很好,但是有几个方面需要商榷,一是,并不是每个函数的错误都以一种方式进行处理,另一方面,听说c语言的函数调用开销相对很高,在函数外面再包上一层会影响性能。
 
封装向来都是由针对性的,对于错误处理,封装的难度非常大,通用型的封装是不适用的,更多的时候是要根据具体业务来处理的.
举例个不是很恰当的例子,比如用CreateFile去打开一个文件,A程序如果打开失败就退出,那么也许可以直接封装一个函数MyOpenFile产生错了,就呼叫exit退出程序. 但是B程序去要在打开错误后尝试新建一个文件,那么MyOpenFile在这里就不适用了.
当然一些基础功能的系统函数还是可以尝试进行通用的错误封装,例如内存操作、内核对象操作等,因为这些错误产生后基本上就只能做同一件事情了,具体封装方法就是呼叫一个全局的资源清理函数,然后exit. 全局清理函数为一个引出的函数指针,由具体的AP来实现,这样就不需要担心资源在退出后无法被释放的问题了.
至于说函数呼叫的开销,除非你现在是在做MCU级别的开发,否则请丢掉这个想法吧. 何况错误处理有一个原则,是允许大部分错误产生后消耗更多的时间去处理的,错误本身就需要付出代价.
 
4. 异常:关于异常的说明和实现可以参考http://xombat.javaeye.com/admin/show/94540,它的优点是能模拟实现c++中异常的一些优点。但是这个异常机制很脆弱,使用时要注意很多问题,而且它的性能开销肯定也会不小。
既然是用C就不要试图去模仿C++的异常处理,本省异常处理就是C++中一个极其难实现的部分,很多情况都只能依赖于操作系统去有效完整的实现.
你的这个帖子我看了,作为一种模拟方法是不错,但是既然用C还在担心函数调用开销问题,那么就简单得使用setjmp longjmp来处理吧,作为一种利用c stack特性的回滚机制还是相当好用的,可以很容易的将错误回滚到一个点上,开销也不大.
 
5. Goto语句:,当发生错误时,利用goto语句跳到相应的错误处理函数中。因为一直以来对goto语句的偏见,和goto语句本身对程序结构性的影响,所以本人一直以来没有用过这种方式,也不知道这种方式会有什么优劣。
 
goto的确破坏代码结构性,但是却十分有效,有不少人对goto嗤之以鼻,那么我只能对这些人嗤之以鼻了.不能因为刀会伤人就不用刀了吧. 合理使用goto会使得错误处理更加简洁明了. 我是比较推辞使用goto来集中错误处理的,这样代码会简洁不少.
总的来说,每个方式都不是尽善尽美的,不知道大家遇到这些问题是怎么处理的?
另外希望还可以讨论如下问题:
1. C语言中的函数调用是不是像一些人说的那样成本很高?
呼叫函数是相对成本高,主要在于处理stack数据,所以如果真的要抠到这个程度,那就尽可能减少函数参数,用指针代替结构等降低stack操作开销. 如果要说到有些cpu call消耗的时钟周期较长或则cache命中失败等问题,那么...你还能写程序吗?
2. 错误处理应该如何区分用户错误和应用程序错误?
当然要区分,用户错误是给用户看的,一般都是用户操作的问题,要让用户了解自己做错了什么,这样可以有效降低以后服务的成本. 而程序自身的错误则是给程序员看的,用来排除程序的bug,这些错误一般情况不应该让客户知道,因为有时候这些错误往往携带了危险数据.
所以一般可以区分error和internal两种大的错误类型,分别处理.
 
3. 如果采用错误封装的方法(方法3),错误处理需要程序结束的地方应该使用exit还是abort?
前面第3种方法有说一部分,无论哪种错误,退出后一定要释放系统无法回收的资源,虽然这个难度有点大.
4. 最好的方法应该是混合使用吧,(比如返回值的方法和全局errno的方法经常混合使用)该用返回值的时候用返回值,该异常的时候异常,但是什么时候该用返回,什么时候该用异常?
其实最好使用统一的处理方法,否则很不容易维护的...
http://19880512.blog.51cto.com/936364/273665
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:431008次
    • 积分:5196
    • 等级:
    • 排名:第5303名
    • 原创:5篇
    • 转载:569篇
    • 译文:0篇
    • 评论:11条
    最新评论