`
xombat
  • 浏览: 163679 次
  • 性别: Icon_minigender_1
  • 来自: 乌托邦
社区版块
存档分类
最新评论

c开发策略-之-错误处理

阅读更多

在使用任何语言进行应用程序开发时,我们都应该提前规划好如何处理错误。Java和c++中普遍使用异常来进行错误处理,但是c语言,因为没有提供一个很优雅的异常机制,所以明确如何进行错误处理显得很重要。C语言中的错误处理有多种方式,总结如下:大家可以讨论这些处理方式的优劣,这样等以后在程序开发中,我们可以从整体上为程序设计更好的错误处理方法。

1. 返回值方式:用函数的返回值标志函数是否执行成功。比如成功返回1,失败返回0。这种方式的好处是简单方便,而且不影响效率,保持了c语言的高效率。但是仍然有问题,一个问题是代码可读性的问题,如果每个函数都有这样的返回值的话,为了保持程序的正确运行,我们必须对每个函数进行正确性验证,就是在调用函数的时候检查他的返回值,这样程序代码很大一部分就可能花费在错误处理上。第二个问题就是函数的返回值冲突的问题。假设strlen函数也可能会出错,使用这种错误处理策略他的返回值应该标志它是否执行成功,但是函数计算的字符串的长度值如何自然地传递出来?最后一个问题可能是最重要的:它不强制你处理错误,而且在不进行处理的情况下,程序仍然能够运行,但结果是不可预知的。

2. 全局errno方式:就是在出现错误的时候,将错误代码记录到一个全局变量errno中。比如waitpid()函数在被信号中断的情况下,将errno设置为EINTR(一宏定义常量)。这种方式解决了返回值方式遇到的返回值冲突问题,而且效率方面也是非常令人愉悦的。但是它要求用户在调用函数后检查errno的值,这种保证是脆弱的,程序仍然有可能在不处理那些errno的情况下”安然”地运行,导致未定义的结果。另一个问题出在多线程方面,errno不是线程安全的,多个线程操作同一个errno会造成混乱。

3. 错误封装:就是将每个有错误返回值的函数分别用一个函数包起来,比如waitpid()函数可以封装成Waitpid()(首字母大写),在这个函数中处理相应的错误。这种错误处理方法可以很好的解决很多问题,应该说效果很好,但是有几个方面需要商榷,一是,并不是每个函数的错误都以一种方式进行处理,另一方面,听说c语言的函数调用开销相对很高,在函数外面再包上一层会影响性能。

4. 异常:关于异常的说明和实现可以参考http://xombat.iteye.com/admin/show/94540,它的优点是能模拟实现c++中异常的一些优点。但是这个异常机制很脆弱,使用时要注意很多问题,而且它的性能开销肯定也会不小。

5. Goto语句:,当发生错误时,利用goto语句跳到相应的错误处理函数中。因为一直以来对goto语句的偏见,和goto语句本身对程序结构性的影响,所以本人一直以来没有用过这种方式,也不知道这种方式会有什么优劣。

总的来说,每个方式都不是尽善尽美的,不知道大家遇到这些问题是怎么处理的?

另外希望还可以讨论如下问题:

1. C语言中的函数调用是不是像一些人说的那样成本很高?

2. 错误处理应该如何区分用户错误和应用程序错误?

3. 如果采用错误封装的方法(方法3),错误处理需要程序结束的地方应该使用exit还是abort?

4. 最好的方法应该是混合使用吧,(比如返回值的方法和全局errno的方法经常混合使用)该用返回值的时候用返回值,该异常的时候异常,但是什么时候该用返回,什么时候该用异常?

分享到:
评论
20 楼 Arath 2007-07-06  
错误不能不处理,
但是也不要耗费太多.
19 楼 Arath 2007-07-06  
xombat 写道
引用
其实最好使用统一的处理方法,否则很不容易维护的...

arath能把自己的具体方案贡献出来不?共同学习,参考,然后讨论一下。


我前面的回复有部分提及,基础函数,主要是内存、内核的操作都是封装的,集中报错,并且引出一个统一的清理函数指针,这样对于这些频繁的系统调用就不要做任何错误判定,直接退出程序.
其他的OS API的错误,并没有使用封装方式处理,因为业务逻辑可能会不一样,所以是使用GetLastErro 和 FormatMessage结合处理的,当然可以做一些简单的归纳分类处理.
业务上的错误汇总在一起分成error和internal处理.
18 楼 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: -.-我用拼音,打快了错字请原谅.
17 楼 xombat 2007-07-06  
引用
其实最好使用统一的处理方法,否则很不容易维护的...

arath能把自己的具体方案贡献出来不?共同学习,参考,然后讨论一下。
16 楼 xombat 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:兄弟用五笔打字,有错别字很难猜呵。
15 楼 Arath 2007-07-06  
qiezi 写道

别什么都是“国人的”,goto的争议可是从老外那边传过来的。我说的限制很清楚,代码写成那样,几乎整个程序逻辑都在一个大函数里面,单元测试都不行。这种大函数要跳出一个深层嵌套只有用goto,goto不是导致大函数的原因,但通常它是一个明显的特征。限制goto只是拆解的一个手段而已,一个最小的限制达到最大效果的手段,只用于新手,就像你说的“合理使用”,那自然是没什么可限制的了,剩下的就只是风格差异。

限制goto增加代码可读性出发点是好的,但是过于使用“限制”“不允许”乃至“敌对”的做法都是不妥当的. 很多事情往往会以讹传讹,所以即使对于新手,应该告之不建议使用,告之逻辑清晰的重要性更为重要.
至于说扣上“国人”二字,的确也是无奈,国外重于争论辩证,而到了国内就会多以“经验”加以重压了.

qiezi 写道

有经验的程序员知道怎么用最少的语法优化达到最多的性能优化,这在代码中就很容易看出水平。架构的问题不在这个讨论范围之内,编码时架构已经确定。很多手工优化方法都不如编译器优化来得好,避免函数调用开销也可以使用内联函数,代码需要保持高可读性并且兼顾性能。

正如你所说,架构已定,则大部分的优化不如交给编译器去做,所以优化问题不能较为绝对的割裂一个开发过程.
对于函数多了增加开销这种说法对于现在大部分的客户应用程序是不需要考虑的,即使是在很多嵌入式系统的开发中也是不需要考虑的. 内联函数现在很多编译器可以自己实现,所以开发人员的优化意识更多的是在全局性和业务性上的.
14 楼 qiezi 2007-07-06  
Arath 写道

这一点就是国人的思维模式问题,因为觉得会有问题,所以就要限制,而不去赋予各自扩展的空间,颇为和谐啊. 大函数的问题的确不好,但是不能归罪于goto的使用吧?

别什么都是“国人的”,goto的争议可是从老外那边传过来的。我说的限制很清楚,代码写成那样,几乎整个程序逻辑都在一个大函数里面,单元测试都不行。这种大函数要跳出一个深层嵌套只有用goto,goto不是导致大函数的原因,但通常它是一个明显的特征。限制goto只是拆解的一个手段而已,一个最小的限制达到最大效果的手段,只用于新手,就像你说的“合理使用”,那自然是没什么可限制的了,剩下的就只是风格差异。
Arath 写道

优化不能过早进行,但是作为一个有经验的程序员应该能够预知部分瓶颈,这样在构架的时候就要做一定的预留,便于回过来优化,以减少后期优化的迭代返工.

有经验的程序员知道怎么用最少的语法优化达到最多的性能优化,这在代码中就很容易看出水平。架构的问题不在这个讨论范围之内,编码时架构已经确定。很多手工优化方法都不如编译器优化来得好,避免函数调用开销也可以使用内联函数,代码需要保持高可读性并且兼顾性能。
13 楼 Arath 2007-07-06  
qiezi 写道
goto简洁高效是没错,错在用的人很容易因为这个小优点而上瘾,最后很容易形成几百行的大函数,用goto跳来跳去,长此以往会形成很多坏习惯。如果强制不使用goto,都会变得很乖,自觉把函数拆成成的函数来调用,因为你没有更好的办法避免重复代码。另一个需要限制的是大括号嵌套层次。当然所有的限制都是强制而不是禁止,例外情况就是性能要求,不过所有优化都应该在后期进行,过早优化是要禁止的。


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

ret不会影响流水线的。函数的返回地址就和Call的跳转地址一样可以预取,预先解码。Call和Ret都是无条件的。

if的预测是没有什么策略来支持的。通常我们说的分支预测怎么样怎么样是应为有for,while等循环存在。
9 楼 xombat 2007-06-30  
to netpcc:
引用
而函数跳转的话,Cache命中率也相当高。

if语句指令的空间局部性更强,命中率应该更高吧。

引用
函数调用操作只有堆栈操作和跳转指令,不会中断流水线,而且栈操作都可以在L2 Cache中完成,

函数调用后,还要返回到调用函数,这时ret指令要从栈中读取地址,也会中断流水线,而且这种情况下pc还无法预测。

除非p4中有个硬件栈,用来存储每次函数调用的返回地址。

if有预测,如果策略足够好的话,中断流水线的概率就很低。并且不会访问mem

我是从处理器的体系结构来考虑的,没有着重偏向哪个处理器的哪个技术。对P4的一些新技术涉及还不算多。
8 楼 qiezi 2007-06-29  
xombat 写道
to qiezi:
引用
“错误封装”我没看出来这个封装的好处,只是业务特定的错误处理方法?

错误处理封装函数最初是在史蒂芬的《unix网络编程:unix APIs》中被提出的,用处很广。
典型的一个错误处理函数Fork()(首字母大写的,区分系统函数fork()):
pid_t Fork(void)
{
  pid_t pid;
  if((pid = fork())<0)
  {
    fprintf(stderr,"Fork error: %s\n",strerror(errno));
    exit(0);
  }
  return pid;
}

依次将所有unix系统调用级函数全部封装,posix函数的封装方法有所不同,他将错误情况存储在code中而不是errno中。

引用
如果是作为库提供,封装一定不能把灵活性、效率任意一方面给封没了

我不认为它能够被当作库来提供,提供这些封装函数只是用来解决处理错误而导致的代码臃肿和代码可读性下降的种种问题,另外因为在不同的情况下对错误的处理情况可能会不同,所以将它们用作库不合适,但可以作为一种错误处理的好的解决方案。

不作为库来提供,自然是没这方面的问题,不过不建议在每个里面都去exit。

xombat 写道

引用
系统实现的errno是线程安全的,它使用了线程专有存储

qiezi有这方面的资料不?我一直以为errno就是一个全局整型变量呢

UNIX网络编程一书的socket api好像就有提到这个。线程专有存储我自己也做过简单的:

// tss.h
#ifndef TSS_H_
#define TSS_H_

#include <pthread.h>

typedef void(*CLEANUP_FUNC)(void* p);

class TSS
{
private:
	pthread_key_t key;

public:
	TSS(CLEANUP_FUNC cleanfunc);
	void set(void* p);
	void* get();
};

#endif // TSS_H_

// tss.cpp
#include "tss.h"

class TSSInitFail : public std::exception
{
};

class TSSSetSpecific : public std::exception
{
};


TSS::TSS(CLEANUP_FUNC f)
{
	int result = pthread_key_create(&key, f);
	if (result != 0)
		throw TSSInitFail();
}

void TSS::set(void* p)
{
	int result = pthread_setspecific(key, p);
	if (result != 0)
		throw TSSSetSpecific();
}

void* TSS::get()
{
	return pthread_getspecific(key);
}

// 一个使用例子:
#define TSS_SIZE (65536 * 4)

void cleanup(void* p)
{
	char* pp = (char*) p;
	delete[] pp;
}

static TSS __formatTSS(&cleanup);


const char* format(const char* fmt, ...)
{
	char* buffer = (char*)__formatTSS.get();
	if (!buffer)
	{
		buffer = new char[TSS_SIZE];
		__formatTSS.set(buffer);
	}

	va_list args;
	va_start(args, fmt);
	vsnprintf(buffer, TSS_SIZE, fmt, args);
	va_end(args);

	return buffer;
}

上面这个例子在多线程情况下,也可以使用const char* p = format("hello %s!", name),因为在同一线程中它总是指向同一个地址,不同线程刚好也不冲突。errno我不知道它如何实现的,通常这种应用需要一个函数才行,看它的头文件的确是个整型变量,不知道有没有哪些编译器选项让它自动生成TSS,VC里面是有的。

补充:找到了errno的实现方式:
http://www.acejoy.com/Html/Article/ace/2520060920135646.html

linux下的代码也找到了,/usr/include/sys/errno.h:
#  if !defined _LIBC || defined _LIBC_REENTRANT
/* When using threads, errno is a per-thread value.  */
#   define errno (*__errno_location ())
#  endif

的确是个函数调用,也有说明“When using threads, errno is a per-thread value”。

xombat 写道

引用
通常一个程序退出还要处理已经打开的资源

操作系统自己会回收已退出的应用程序的资源,为什么我们还要操心。我一直以来在exit之前都没有考虑过回收资源的问题,要不你怎么解决?

通常程序在退出前关闭资源是合理的,我们有些cache服务器使用了BDB,程序直接退出会很严重,BDB没有正常关闭,甚至有些线程没有关闭,所以下次重启服务器就起不来,虽然可以手动修复BDB,还是会丢失一些数据的,而且还影响了服务器在线时间。socket通常也要关闭的,比如listen一个端口,直接exit再重启程序会发现端口没有关闭。写服务器程序,我最先考虑的就是如何初始化、如何动态重新加载配置、如何安全退出、如何在异常退出后修复数据,把这几个考虑好了再写其它逻辑。
7 楼 netpcc 2007-06-29  
to xombat
成本不是按照指令个数来判断的。

函数调用操作只有堆栈操作和跳转指令,不会中断流水线,而且栈操作都可以在L2 Cache中完成,而函数跳转的话,Cache命中率也相当高。所以一次函数调用不过10来个时钟周期。而if可能会造成流水线中断(分支预测失败),对于P4这样的CPU,重新填充流水线需要几十(>40)个时钟周期。

在C程序中,跨函数调用处理异常并不是什么好主意。一般来说用返回值就够了,errno起辅助性作用。就像Windows API和c runtime这样。
你给出的那个用C实现Exception的方法会引入一定的OverHead。如果你能接受OverHead的话,为什么不用C++呢?做为一种更强的C来用好了。不用Class就是了。
6 楼 RyanPoy 2007-06-29  
我一般采用返回值+日志方式。不过用起来不是很爽。
5 楼 xombat 2007-06-28  
to netpcc:
引用
各种函数调用方式的开销差不多,总的来说比if等分支语句要小

函数调用要压栈,call,ret,出栈,典型的if语句只要cmpl,jmp就行了,函数调用比if开销小这还是第一次听说。

引用
在c程序里用goto实现类似于异常中的catch和final还是很方便的

goto语句能从一个函数调到另一个函数吗?所以他根本无法模拟异常的好处。
4 楼 xombat 2007-06-28  
to qiezi:
引用
“错误封装”我没看出来这个封装的好处,只是业务特定的错误处理方法?

错误处理封装函数最初是在史蒂芬的《unix网络编程:unix APIs》中被提出的,用处很广。
典型的一个错误处理函数Fork()(首字母大写的,区分系统函数fork()):
pid_t Fork(void)
{
  pid_t pid;
  if((pid = fork())<0)
  {
    fprintf(stderr,"Fork error: %s\n",strerror(errno));
    exit(0);
  }
  return pid;
}

依次将所有unix系统调用级函数全部封装,posix函数的封装方法有所不同,他将错误情况存储在code中而不是errno中。

引用
如果是作为库提供,封装一定不能把灵活性、效率任意一方面给封没了

我不认为它能够被当作库来提供,提供这些封装函数只是用来解决处理错误而导致的代码臃肿和代码可读性下降的种种问题,另外因为在不同的情况下对错误的处理情况可能会不同,所以将它们用作库不合适,但可以作为一种错误处理的好的解决方案。

引用
系统实现的errno是线程安全的,它使用了线程专有存储

qiezi有这方面的资料不?我一直以为errno就是一个全局整型变量呢

引用
通常一个程序退出还要处理已经打开的资源

操作系统自己会回收已退出的应用程序的资源,为什么我们还要操心。我一直以来在exit之前都没有考虑过回收资源的问题,要不你怎么解决?

引用
C不是没提供异常嘛

这里实现了一个:http://xombat.iteye.com/admin/show/94540
3 楼 netpcc 2007-06-28  
各种函数调用方式的开销差不多,总的来说比if等分支语句要小。所以不必担心函数调用的开销。

在c程序里用goto实现类似于异常中的catch和final还是很方便的。只要运用得当,goto能够使程序的流程和结构更清晰。
2 楼 jigsaw 2007-06-28  
KISS is one of the principles of C.
errno and negative return value compose a perfect solution as can be seen in kernels.
try/catch is by no means elegant as far as C is concerned. Imagine someday you are asked to inline Java code in C.
As I've mentioned, the kernel _can_ be designed so that exception stack is open to user space. Hence you will see try/catch is no longer valuable.
However, I don't like the idea of make it up in user space, though it looks cute.
BTW, sometimes panic routine is the way out.
1 楼 qiezi 2007-06-28  
系统实现的errno是线程安全的,它使用了线程专有存储,你自己实现的的errno也应该这样。返回值方式和errno方式相似,不过通常一些库分返回-1并设置errno,比如socket的recv,因为它还要返回0和正数值,另外使用一个errno就可以把错误号设置为任意值,否则只能使用负数了。

“错误封装”我没看出来这个封装的好处,只是业务特定的错误处理方法?如果是作为库提供,封装一定不能把灵活性、效率任意一方面给封没了。

引用

1. C语言中的函数调用是不是像一些人说的那样成本很高?

函数调用的成本主要是参数压栈、跳转,我一直不认为这个问题很明显呢,现在的计算机都这么强,而且不是经常听说不要过早优化嘛。

我前2天在维护同事的一份代码,一个长函数,600多行,一堆goto,现在被我拆得每个函数都不超过20行,函数调用效率上的降低是难以察觉的,而且这个程序本来CPU占用也不到10%。
引用

2. 错误处理应该如何区分用户错误和应用程序错误?

一般对于数据处理、服务器程序,都要确定对于输入数据的错误如何处理,简单丢掉或者程序退出都是可能的选项。应用程序错误?如果是磁盘满、数据库连接不上等等,依旧要确定程序如何动作。通常对于一个不间断运行的程序,任何错误都要写日志,不同的是根据程序的需求确定日志的错误级别。
引用

3. 如果采用错误封装的方法(方法3),错误处理需要程序结束的地方应该使用exit还是abort?

我依旧看不出这种封装的好处,无论是exit还是其它操作,你这个封装都是个降低灵活性的,通常一个程序退出还要处理已经打开的资源。

引用

4. 最好的方法应该是混合使用吧,(比如返回值的方法和全局errno的方法经常混合使用)该用返回值的时候用返回值,该异常的时候异常,但是什么时候该用返回,什么时候该用异常?

C不是没提供异常嘛,C++提供了,不过效率很低的。什么时候使用什么方式取决于编码风格、效率要求等因素。

相关推荐

    C语言开发----c语言矿井逃生.rar

    在本资源"C语言开发----c语言矿井逃生.rar"中,我们可以看到一个关于使用C语言进行编程实践的项目,主题是“矿井逃生”。这个项目旨在帮助学习者深入理解和运用C语言的基础知识,同时锻炼问题解决和逻辑思维能力。...

    C语言开发----c语言UDP传输系统源码.rar

    本项目“C语言开发----c语言UDP传输系统源码”将深入探讨如何利用C语言实现用户数据报协议(UDP)的传输系统。UDP是互联网协议族中的一个无连接、不可靠的传输协议,常用于需要快速传输但不要求顺序到达或数据完整性...

    C语言开发----c语言五子棋源码.rar

    C语言开发五子棋游戏涉及到以下几个关键知识点: 1. **基本数据结构**:在C语言中,数组常用于表示棋盘。可以创建一个二维数组来代表棋盘上的每一个位置,其中每个元素存储棋子的颜色(例如,0表示空位,1表示黑棋...

    C语言开发----C语言超市管理系统.rar

    《C语言超市管理系统》是一个基于C语言开发的实用程序,旨在模拟实际超市的运营流程,包括商品管理、库存控制、销售记录以及客户交易等多个环节。这个项目为学习C语言编程的初学者提供了一个实践平台,有助于提升...

    C语言开发----c语言盒子接球游戏源码.rar

    8. **错误处理**:良好的程序应该能够处理可能出现的错误情况,比如无效用户输入或资源耗尽。源码中可能包含一些错误处理代码,如检查分配内存是否成功或在遇到异常时恢复程序状态。 9. **游戏逻辑**:接球游戏的...

    C语言项目开发--贪吃蛇

    2. **指针操作**:C语言的指针是其强大之处,允许直接访问内存地址,实现高效的数据操作和内存管理。 3. **预处理器**:预处理器负责处理#define宏定义、包含头文件(#include)等任务。 4. **函数**:C语言使用函数来...

    C语言范例开发大全--源代码

    《C语言范例开发大全--源代码》是一个深入学习C语言的教程,它包含了丰富的实例和源代码,旨在帮助开发者全面理解并掌握C语言的基本概念、语法和编程技巧。这个教程共分为十九个课时,每个课时都围绕一个特定的主题...

    C语言开发----c语言支持自己创建迷宫,并求解最短路径.rar

    7. **错误处理**:在实际编程中,需要考虑输入有效性检查,例如迷宫尺寸是否合法,以及边界条件处理,确保程序在各种情况下都能正确运行。 8. **代码优化**:为了提高效率,可以对算法进行优化,例如使用位运算减少...

    深入掌握Objective-C中的错误处理机制

    记住,良好的错误处理策略是专业软件开发的关键。 本文详细介绍了Objective-C中的错误处理机制,包括NSError的使用、NSException的捕获和抛出,以及返回错误码的方法。通过实际代码示例,展示了如何在不同的编程...

    三国志游戏源代码(C语言)-----经典游戏的源码

    4. **错误处理和调试**:源代码中应包含错误处理机制,以确保在遇到异常情况时程序不会崩溃。 5. **内存管理**:C语言要求手动管理内存,因此了解如何分配和释放内存对避免内存泄漏至关重要。 6. **文件I/O**:...

    C语言规范标准-C99(中文版)

    - 强化了错误处理机制,如`assert.h`库中的断言功能,帮助调试程序。 - 改进了预处理器,支持条件编译指令的嵌套。 C99标准的推出极大地扩展了C语言的能力,提高了代码的可读性和可维护性,同时也为跨平台开发...

    C语言开发-经典游戏-像素蜘蛛纸牌

    在本项目中,我们探索的是使用C语言进行经典游戏——像素蜘蛛纸牌的开发。C语言是一种基础且高效的编程语言,常用于系统级编程、游戏开发以及嵌入式系统等。在这个项目中,开发者选择了DevC++ EGE图形库来实现游戏的...

    深入体会C语言项目开发_-_俄罗斯方块游戏

    5. **错误处理与调试**:添加异常处理代码,使用调试工具查找并修复程序错误。 #### 5. 总结 通过使用C语言开发俄罗斯方块游戏,不仅可以深入理解游戏设计的核心原理,还能掌握项目管理和软件工程的基本方法。这一...

    C语言运行环境 -----汉语.txt

    - **Errors (错误)**:配置错误处理策略,如在出现25个错误后停止构建等。 - **Warnings (警告)**:配置警告处理,如在出现100个警告后停止构建等。 - **Display warnings (显示警告)**:设置是否显示警告信息。 ...

    C语言程序设计---图书登记管理系统.zip

    5. 错误处理:良好的错误处理机制是程序健壮性的保证。在C语言中,可以使用if语句检查函数返回值,比如检查文件打开是否成功,数据读取是否有误等。当遇到问题时,程序应给出清晰的错误提示。 6. 用户界面:虽然...

    C语言实现 -- 一种混合并行XML解析实现.zip

    C语言实现可能包含自定义的错误处理机制,如返回错误代码或抛出异常。 5. 内存管理:由于C语言没有自动的垃圾回收机制,解析器需要手动管理内存,包括分配和释放。这需要谨慎处理,以避免内存泄漏和悬挂指针。 6. ...

    C语言程序设计---学生宿舍管理系统.doc

    学生宿舍管理系统是一个基于C语言开发的应用程序,旨在简化对学生宿舍信息的管理和查询工作。系统分为四个主要部分:需求分析、概要设计、详细设计和调试分析,以及用户说明和课程设计总结。本文将深入探讨其核心...

    Object C 错误处理

    iOS开发中,Objective-C语言作为一种面向对象的编程语言,具备成熟的错误处理机制。错误处理是程序设计中非常关键的一环,尤其是在开发过程中需要妥善处理各种异常情况。在Apple官方文档中详细介绍了如何在Cocoa框架...

Global site tag (gtag.js) - Google Analytics