大凡写程序者,都会遇到错误;
大凡写程序者也都知道两种错误处理的机制:传统的'错误码返回机制'和'面向对象语言引入的异常处理机制'。

人们常常会在这两种机制之间徘徊不定,难以抉择。但有两类人大可不必为此头痛,他们是坚决只使用'错误码返回机制'的人,和坚决只使用'异常处理机制'的人。而苦就苦了摇摆在中间,思索不定的那些人了。这群人有一个特点就是不停的问:"什么是异常?什么时候该使用错误码返回?什么时候又要用标准的异常处理机制呢?内存不足是不是异常?网络瘫痪是不是异常?用户输入id的超出了允许的长度该如何处理?"等
等诸如此类的问题。

我一直使用C,C没有像C++、Java、Ruby那样提供标准的异常处理机制,C只是提供了setjmp和longjmp这样的粗糙的甚至让很多新人觉得迷惑的调用接口,所以到目前为止,我还没有真正用过"异常处理"来写过代码。直到我看到"David Hanson "的"c interfaces and implementations(C语言接口与实现)"中第4章封装的C的异常处理宏。看完后我有些迷惑。迷惑的不是这组宏有什么精湛技艺,而是他破坏了以前我对错误处理的单一使用错误码返回的想法,他又给了我一个选择:使用异常处理。而多了一种选择之后,我也陷入徘徊不定中。只能反复的一遍又一遍的看和回味Hanson在这章起始的那段关于错误分类的描述和理解,以寻求在返回错误码和使用异常处理之间的平衡。

Hanson将错误分为三类:
用户错误,就是由用户的不正确输入引起的,对于此类错误的态度是:函数必须处理错误并返回错误代码。
//testusererr.c
int main(int argc, char *argv[]) {
if (argc <= 1) {
printf("you should input at leaset an argument!\n");
return 1;
}

int i = atoi(argv[1]);

if (i < 5) {
printf("i should be more than 5.\n");
return 2;
}

return 0;
}

上述的例子仅是一个程序接收用户的输入,并对其输入错误进行处理。

那么对于一个功能接口而言,对其参数是否都要做类似处理呢?通常来说在我们系统中除了针对用户的接口需要进行外,其他的接口都是内部调用的,比如库,库提供了接口同时也隐含了某种约定。我们作为程序员在使用库的时候都会遵守约定。但是这是不是这些库接口就不用对其接口参数进行任何处理了呢。一般我们会在接口的入口处加上断言。这就是我们要说的第二种错误–可检查的运行时错误。

按照Hanson的说法,可检查的运行时错误不是用户错误,上面已经说了,程序员已经按照约定传入了适当的参数,那么一旦出现错误,这个错误是从何而来的呢?可检查的运行时错误恰恰是揭示了程序的漏洞,他们不可预料,通常遇到此类错误,应用程序一般将无法恢复。看下面的例子:

//testcheckedrterr.c
#include
#include

#define MY_MAGIC 0×19210723

struct Foo {
int i;
char id[22];
#ifdef _DEBUG
unsigned int magic;
#endif

};

void check_foo(const struct Foo *foo) {
assert(foo != NULL);
#ifdef _DEBUG
assert(foo->magic == MY_MAGIC);
#endif
;
}

int main() {
struct Foo foo;
foo.i = 13;
#ifdef _DEBUG
foo.magic = MY_MAGIC;
#endif
strcpy(foo.id, "this will cause an overflow");

check_foo(&foo);
}

gcc -D_DEBUG -g *.c
执行的结果:
Assertion failed: foo->magic == MY_MAGIC, file testcheckedrterr.c, line 20
退出 ((主存储器)信息转储)

这里程序的确是按照check_foo的需要的参数提供了参数,只是没有预料到,程序存在栈溢出,对于这种运行时错误,在check_foo中我们使用了断言。断言一般都是无法恢复的,直接的结果就是程序退出。

异常,第三类错误,极少出现,可能不可预测,但有可能从中恢复的错误。如:内存不足、网络瘫痪、磁盘空间满等。按照Hanson的经验,由于异常发生很少,很多可能发生此类异常的函数都是没有返回值的。这时是采用异常处理机制的好时机。

说到这,也许还只是停留在说教上,也许开头提到的那些疑问仍然无法回答。我想什么样的回答都不能令所有人满意,自己在项目中摸索理解吧。其实永远没有绝对的事情,你大可在程序中丝毫不考虑使用异常机制。

© 2007, bigwhite. 版权所有.

Related posts:

  1. APR源代码分析-网络IO篇
  2. P.J.Plauger版本C标准库实现分析之'assert.h'
  3. APR源代码分析-线程同步篇
  4. 线程函数参数引发的问题
  5. Java 5.0新特性研究(二)