C 語言缺乏原生異常處理,但標準庫提供了 <setjmp.h> 中的 setjmp / longjmp,構成了非局部跳轉的基石。本文從原理到實戰,帶你構建一套可嵌套、帶異常對象、支持 FINALLY 的結構化異常框架 Except。
#includejmp_buf env; // 保存寄存器、棧指針、程序計數器等上下文 int setjmp(jmp_buf env); // 首次調用返回 0;從 longjmp 返回時返回非 0 void longjmp(jmp_buf env, int val); // 觸發非局部跳轉,val 傳回 setjmp
goto 只能函數內跳轉的限制jmp_buf 實為數組結構體,存放關鍵寄存器狀態setjmp 返回 0 表初始化,非 0 表異常返回static jmp_buf buf;
void second() { printf("second\n"); longjmp(buf, 1); }
void first() { second(); printf("first\n"); } // 不會執行
int main() {
if (!setjmp(buf)) first(); // try 區塊
else printf("main\n"); // catch 區塊
return 0;
}
// 輸出:second → main
為了支持嵌套異常、異常對象攜帶信息、FINALLY 清理,需維護一個異常幀鏈表:
typedef struct Except_Frame {
struct Except_Frame *prev; // 上一幀指標
jmp_buf env; // 跳轉環境
const char *file; // 異常拋出文件
int line; // 行號
const Except_T *exception; // 異常對象指標
} Except_Frame;
Except_Frame *Except_stack = NULL; // 全局/線程局部棧頂
// except.h #ifndef EXCEPT_INCLUDED #define EXCEPT_INCLUDED #include#define T Except_T typedef struct T { const char *reason; } T; // 異常類型:僅攜帶原因字串 // 拋出異常 #define RAISE(e) Except_raise(&(e), __FILE__, __LINE__) void Except_raise(const T *e, const char *file, int line); // 捕獲語法(宏實現) #define TRY do { ... } while(0) #define EXCEPT(e) ... #define ELSE ... #define FINALLY ... #define END_TRY ... #define RERAISE Except_raise(Except_frame.exception, Except_frame.file, Except_frame.line) #define RETURN switch (Except_stack = Except_stack->prev, 0) default: return #endif
TRY 壓入新幀並調用 setjmp;EXCEPT 比對異常類型;FINALLY 無條件執行清理並重拋異常;END_TRY 彈出幀,若異常未處理則 RERAISE。
// 關鍵狀態枚舉
enum { Except_entered=0, Except_raised, Except_handled, Except_finalized };
// except.c
#ifdef WIN32
__declspec(thread) // 線程局部存儲,支持多線程
#endif
Except_Frame *Except_stack = NULL;
void Except_raise(const T *e, const char *file, int line) {
Except_Frame *p = Except_stack;
assert(e);
if (p == NULL) { // 未捕獲異常 → 終止程序
fprintf(stderr, "Uncaught exception");
if (e->reason) fprintf(stderr, " %s", e->reason);
if (file && line > 0) fprintf(stderr, " at %s:%d", file, line);
fprintf(stderr, "\naborting...\n");
fflush(stderr);
abort();
}
p->exception = e;
p->file = file;
p->line = line;
Except_stack = p->prev; // 彈出當前幀
longjmp(p->env, Except_raised); // 跳回 TRY 的 setjmp
}
assert(condition):僅在 Debug 構建生效,釋出版本自動消失,用於檢查「不可能為假」的前置條件(如除數非零)__declspec(thread),POSIX 用 pthread_key_create 實現 Except_stack 線程隔離《C Interfaces and Implementations: Techniques for Creating Reusable Software》— David R. Hanson,第 4 章「Exceptions」