锁定老帖子 主题:c开发策略-之-错误处理
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2007-07-02
一旦中断流水线,Cache命中就毫无意义了。因为重新填充流水线的成本非常高。
ret不会影响流水线的。函数的返回地址就和Call的跳转地址一样可以预取,预先解码。Call和Ret都是无条件的。 if的预测是没有什么策略来支持的。通常我们说的分支预测怎么样怎么样是应为有for,while等循环存在。 |
|
返回顶楼 | |
发表时间:2007-07-06
错误处理除了需要了解现在执行的情况外,同时还需要了解具体错了什么,已备后续的维护追踪.
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.iteye.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的方法经常混合使用)该用返回值的时候用返回值,该异常的时候异常,但是什么时候该用返回,什么时候该用异常? 其实最好使用统一的处理方法,否则很不容易维护的... |
|
返回顶楼 | |
发表时间:2007-07-06
goto简洁高效是没错,错在用的人很容易因为这个小优点而上瘾,最后很容易形成几百行的大函数,用goto跳来跳去,长此以往会形成很多坏习惯。如果强制不使用goto,都会变得很乖,自觉把函数拆成成的函数来调用,因为你没有更好的办法避免重复代码。另一个需要限制的是大括号嵌套层次。当然所有的限制都是强制而不是禁止,例外情况就是性能要求,不过所有优化都应该在后期进行,过早优化是要禁止的。
|
|
返回顶楼 | |
发表时间:2007-07-06
qiezi 写道 goto简洁高效是没错,错在用的人很容易因为这个小优点而上瘾,最后很容易形成几百行的大函数,用goto跳来跳去,长此以往会形成很多坏习惯。如果强制不使用goto,都会变得很乖,自觉把函数拆成成的函数来调用,因为你没有更好的办法避免重复代码。另一个需要限制的是大括号嵌套层次。当然所有的限制都是强制而不是禁止,例外情况就是性能要求,不过所有优化都应该在后期进行,过早优化是要禁止的。
呵呵,这一点就是国人的思维模式问题,因为觉得会有问题,所以就要限制,而不去赋予各自扩展的空间,颇为和谐啊. 大函数的问题的确不好,但是不能归罪于goto的使用吧? 优化不能过早进行,但是作为一个有经验的程序员应该能够预知部分瓶颈,这样在构架的时候就要做一定的预留,便于回过来优化,以减少后期优化的迭代返工. 问题对事不对人,以上是我个人的观点 |
|
返回顶楼 | |
发表时间:2007-07-06
Arath 写道 这一点就是国人的思维模式问题,因为觉得会有问题,所以就要限制,而不去赋予各自扩展的空间,颇为和谐啊. 大函数的问题的确不好,但是不能归罪于goto的使用吧? 别什么都是“国人的”,goto的争议可是从老外那边传过来的。我说的限制很清楚,代码写成那样,几乎整个程序逻辑都在一个大函数里面,单元测试都不行。这种大函数要跳出一个深层嵌套只有用goto,goto不是导致大函数的原因,但通常它是一个明显的特征。限制goto只是拆解的一个手段而已,一个最小的限制达到最大效果的手段,只用于新手,就像你说的“合理使用”,那自然是没什么可限制的了,剩下的就只是风格差异。 Arath 写道 优化不能过早进行,但是作为一个有经验的程序员应该能够预知部分瓶颈,这样在构架的时候就要做一定的预留,便于回过来优化,以减少后期优化的迭代返工. 有经验的程序员知道怎么用最少的语法优化达到最多的性能优化,这在代码中就很容易看出水平。架构的问题不在这个讨论范围之内,编码时架构已经确定。很多手工优化方法都不如编译器优化来得好,避免函数调用开销也可以使用内联函数,代码需要保持高可读性并且兼顾性能。 |
|
返回顶楼 | |
发表时间:2007-07-06
qiezi 写道 别什么都是“国人的”,goto的争议可是从老外那边传过来的。我说的限制很清楚,代码写成那样,几乎整个程序逻辑都在一个大函数里面,单元测试都不行。这种大函数要跳出一个深层嵌套只有用goto,goto不是导致大函数的原因,但通常它是一个明显的特征。限制goto只是拆解的一个手段而已,一个最小的限制达到最大效果的手段,只用于新手,就像你说的“合理使用”,那自然是没什么可限制的了,剩下的就只是风格差异。 限制goto增加代码可读性出发点是好的,但是过于使用“限制”“不允许”乃至“敌对”的做法都是不妥当的. 很多事情往往会以讹传讹,所以即使对于新手,应该告之不建议使用,告之逻辑清晰的重要性更为重要. 至于说扣上“国人”二字,的确也是无奈,国外重于争论辩证,而到了国内就会多以“经验”加以重压了. qiezi 写道 有经验的程序员知道怎么用最少的语法优化达到最多的性能优化,这在代码中就很容易看出水平。架构的问题不在这个讨论范围之内,编码时架构已经确定。很多手工优化方法都不如编译器优化来得好,避免函数调用开销也可以使用内联函数,代码需要保持高可读性并且兼顾性能。 正如你所说,架构已定,则大部分的优化不如交给编译器去做,所以优化问题不能较为绝对的割裂一个开发过程. 对于函数多了增加开销这种说法对于现在大部分的客户应用程序是不需要考虑的,即使是在很多嵌入式系统的开发中也是不需要考虑的. 内联函数现在很多编译器可以自己实现,所以开发人员的优化意识更多的是在全局性和业务性上的. |
|
返回顶楼 | |
发表时间:2007-07-06
to Arath:
引用 这里还涉及一个使用习惯的问题,一般来说返回0位正确,非0为错误,但是也有颠倒的,这里和个人编码习惯有关,所以要特别注意就是了.
unix的系统调用级函数(和一些老的posix函数)的函数返回指既包括错误代码,也包括有用的结果。0表示成功,-1表示失败。 新的posix函数返回值只包括错误代码,成功0,非0表示失败。有用的结果通过值-结果方式(这个名词是值-结果还是引用-结果我记不清了)来传递。 标准c库中的函数返回值一般1表示成功,0或NULL表示失败。 引用 这里特别要推荐在windows下开发,处理错误应该尽量使用GetLastError组合FormatMessage,而非errno.
在windows环境下用纯c开发有什么意义?windows环境中什么情况下有必要用c开发? ps:兄弟用五笔打字,有错别字很难猜呵。 |
|
返回顶楼 | |
发表时间:2007-07-06
引用 其实最好使用统一的处理方法,否则很不容易维护的...
arath能把自己的具体方案贡献出来不?共同学习,参考,然后讨论一下。 |
|
返回顶楼 | |
发表时间:2007-07-06
xombat 写道 unix的系统调用级函数(和一些老的posix函数)的函数返回指既包括错误代码,也包括有用的结果。0表示成功,-1表示失败。 新的posix函数返回值只包括错误代码,成功0,非0表示失败。有用的结果通过值-结果方式(这个名词是值-结果还是引用-结果我记不清了)来传递。 标准c库中的函数返回值一般1表示成功,0或NULL表示失败。 恩,所以比较麻烦,我个人倾向使用0表示成功,非0位失败. 引用 在windows环境下用纯c开发有什么意义?windows环境中什么情况下有必要用c开发? ps:兄弟用五笔打字,有错别字很难猜呵。 那个...个人习惯,而且很少做UI的东西. ps: -.-我用拼音,打快了错字请原谅. |
|
返回顶楼 | |
发表时间:2007-07-06
xombat 写道 引用 其实最好使用统一的处理方法,否则很不容易维护的...
arath能把自己的具体方案贡献出来不?共同学习,参考,然后讨论一下。 我前面的回复有部分提及,基础函数,主要是内存、内核的操作都是封装的,集中报错,并且引出一个统一的清理函数指针,这样对于这些频繁的系统调用就不要做任何错误判定,直接退出程序. 其他的OS API的错误,并没有使用封装方式处理,因为业务逻辑可能会不一样,所以是使用GetLastErro 和 FormatMessage结合处理的,当然可以做一些简单的归纳分类处理. 业务上的错误汇总在一起分成error和internal处理. |
|
返回顶楼 | |