C 语言没有内置异常机制,错误传播通常依赖返回值或全局错误码,调用链一长就容易丢失上下文。1989 年 DEC 的 Robert Roberts 基于 Modula-2+ 的异常模型,用 setjmp/longjmp 实现了一套可移植的 try-except-finally 宏,核心思路是:在 TRY 块入口保存栈帧,异常发生时沿上下文链表向上查找匹配的 EXCEPT 子句,找到后 longjmp 回跳。
下面按头文件、实现文件、测试程序三部分走读源码,最后说明执行流程和需要注意的边界。
头文件:宏展开与上下文结构
text
/* Copyright 1989 Digital Equipment Corporation. */
/* Distributed only by permission. */
/**************************************************************************/
/* File: exception.h */
/* Last modified on Wed Mar 15 16:40:41 PST 1989 by roberts */
/* */
/* The exception package provides a general exception handling mechanism */
/* for use with C that is portable across a variety of compilers and */
/* operating systems. The design of this facility is based on the */
/* exception handling mechanism used in the Modula-2+ language at DEC/SRC */
/* and is described in detail in the paper in the documents directory. */
/* For more background on the underlying motivation for this design, see */
/* SRC Research Report #3. */
/**************************************************************************/
#include <setjmp.h>
#include <stdlib.h>
#define MaxExceptionsPerScope 10
#define ETooManyExceptClauses 101
#define EUnhandledException 102
#define ES_Initialize 0
#define ES_EvalBody 1
#define ES_Exception 2
typedef struct { char *name ;} exception;
typedef struct _ctx_block {
jmp_buf jmp;
int nx;
exception *array[MaxExceptionsPerScope];
exception *id;
int value;
int finally;
struct _ctx_block *link;
} context_block;
extern exception ANY;
extern context_block *exceptionStack;
extern void _RaiseException(exception *e, int v);
#define RAISE(e, v) _RaiseException(&e, v)
#define TRY \
{\
context_block _ctx;\
int _es = ES_Initialize;\
_ctx.nx = 0;\
_ctx.link= NULL;\
_ctx.finally = 0;\
_ctx.link = exceptionStack;\
exceptionStack = &_ctx;\
if (setjmp(_ctx.jmp) != 0) _es = ES_Exception;\
while (1) {\
if (_es == ES_EvalBody) {
#define EXCEPT(e) \
if(_es == ES_EvalBody) exceptionStack = _ctx.link;\
break;\
}\
if (_es == ES_Initialize) {\
if (_ctx.nx >= MaxExceptionsPerScope)\
exit(ETooManyExceptClauses);\
_ctx.array[_ctx.nx++] = &e;\
} else if (_ctx.id == &e || &e == &ANY) {\
int exception_value = _ctx.value;\
exceptionStack = _ctx.link;
#define FINALLY \
}\
if (_es == ES_Initialize) {\
if (_ctx.nx >= MaxExceptionsPerScope)\
exit(ETooManyExceptClauses);\
_ctx.finally = 1;\
} else {\
exceptionStack = _ctx.link;
#define ENDTRY \
if (_ctx.finally && _es == ES_Exception)\
_RaiseException(_ctx.id, _ctx.value);\
break;\
}\
_es = ES_EvalBody;\
}\
}
几个关键点:
context_block 是异常上下文,包含 jmp_buf(保存跳转目标)、已注册的异常类型数组、当前异常 id/value、是否含 FINALLY 子句,以及指向外层上下文的 link 指针,形成一条链。
/* Copyright 1989 Digital Equipment Corporation. */
/* Distributed only by permission. */
/**************************************************************************/
/* File: exception.h */
/* Last modified on Wed Mar 15 16:40:42 PST 1989 by roberts */
/* */
/* Implementation of the C exception handler. Much of the real work is */
/* done in the exception.h header file. */
/**************************************************************************/
#include <stdio.h>
#include "exception.h"
context_block *exceptionStack = NULL;
exception ANY;
void _RaiseException(exception *e, int v){
context_block *cb, *xb;
exception *t;
int i, found;
found = 0;
for (xb = exceptionStack; xb != NULL; xb = xb->link) {
for (i = 0; i < xb->nx; i++) {
t = xb->array[i];
if (t == e || t == &ANY) {
found = 1;
break;
}
}
if (found) break;
}
if (xb == NULL) exit(EUnhandledException);
for (cb = exceptionStack; cb != xb && !cb->finally; cb = cb->link);
exceptionStack = cb;
cb->id = e;
cb->value = v;
longjmp(cb->jmp, ES_Exception);
}