之前总结了一下 setjmp 的使用:
JuJu:C语言拾遗:setjmpzhuanlan.zhihu.com我个人在开发中实际上没用过 setjmp 这个东西,但它毕竟存在在那里。C语言标准库的函数用指头数的过来,所以想搞明白它。
这篇笔记打算总结一下,怎么在C语言中实现异常。
异常是一个运行时错误,在C语言中,可以用assert来处理运行时错误,但它会直接abort掉程序。
面对异常,可以有三种选择:
一种是不管它,那么它就会abort程序。
一种是捕获它,然后做特殊处理。
还有一种是在异常处理代码中重新抛出它,让更外层的异常处理代码处理它。
比如:
RAISE(Mem_failed);
这里直接抛了一个异常对象 Mem_failed ,没有异常处理代码,那么程序输出错误信息,abort。
又比如:
TRY {
RAISE(Mem_failed);
}
EXCEPT(Mem_failed) {
/* catch */
}
END_TRY;
这里捕获了异常对象 Mem_failed ,那么程序继续往下执行。
异常对象可以简单地定义成一个全局的对象:
typedef struct ExceptObj {
const char* name;
} ExceptObj ;
ExceptObj Mem_failed = { "Out of memory" };
TRY是代码的起始点,在RAISE一个异常的时候,要跳回此处。用一个数据结构表示它,起名叫异常帧:
typedef struct ExceptFrame {
jmp_buf env;
ExceptObj *except;
const char *file;
int line;
int status;
} ExceptFrame;
env 是setjmp 和longjmp使用的,status是longjmp的返回值,从而表达一个异常情况,取值范围定义成:
enum { EXCEPT_ENTER, EXCEPT_RAISE, EXCEPT_HANDLE };
file 和 line 在RAISE异常时,被设置成此时的文件名和行号。
现在代码可以写成这样:
ExceptFrame frame;
frame.status = setjmp(frame.env);
if (frame.status == EXCEPT_ENTER) {
// our normal code, raise an exception
frame.file = __FILE__;
frame.line = __LINE__;
frame.except = &Mem_failed;
longjmp(frame.env, EXCEPT_RAISE);
}
else if (&Mem_failed == frame.except) {
frame.status = EXCEPT_HANDLE;
// our handle code
}
// uncaught, abort
if (frame.status == EXCEPT_RAISE) {
fprintf(stderr, "an exception %s occured at %s:%dn", frame.except->name, frame.file, frame.line);
fflush(stderr);
abort();
}
else if 分支捕获了异常,并设置好了处理状态。如果没有,那么最后就会被abort。
完整的测试代码如:
#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
enum { EXCEPT_ENTER, EXCEPT_RAISE, EXCEPT_HANDLE };
typedef struct ExceptObj {
const char* name;
} ExceptObj ;
ExceptObj Mem_failed = { "Out of memory" };
typedef struct ExceptFrame {
jmp_buf env;
ExceptObj *except;
const char *file;
int line;
int status;
} ExceptFrame;
int main()
{
ExceptFrame frame;
frame.status = setjmp(frame.env);
if (frame.status == EXCEPT_ENTER) {
// our normal code, raise an exception
frame.file = __FILE__;
frame.line = __LINE__;
frame.except = &Mem_failed;
longjmp(frame.env, EXCEPT_RAISE);
}
/*else if (&Mem_failed == frame.except) {
frame.status = EXCEPT_HANDLE;
// our handle code
}*/
// uncaught, abort
if (frame.status == EXCEPT_RAISE) {
fprintf(stderr, "an exception %s occured at %s:%dn", frame.except->name, frame.file, frame.line);
fflush(stderr);
abort();
}
printf("Happy enddingn");
return 0;
}
但这样的处理只能是玩具,没有实用价值,因为代码编写的很繁琐,而且不能 RERAISE ,也没有清理机制。
这里有一个比较完善的代码可供参考,由于使用了宏造成了大量扭曲代码,就不做相关笔记了。
demon90s/CLibgithub.com