程序運行中常見三類錯誤:用戶輸入錯誤、運行期錯誤(如內存不足)、以及異常情況(如除零、非法訪存)。C 語言本身缺乏結構化異常處理機制,標準庫提供的 setjmp 和 longjmp 成為實現非局部跳轉、模擬異常處理的基石。
#include <setjmp.h> int setjmp(jmp_buf env); // 保存當前調用環境至 env,首次返回 0 void longjmp(jmp_buf env, int val); // 恢復 env 環境,使 setjmp 返回 val (val=0 時返回 1)
longjmp 跳回時返回非零值(即 val)。env 保存的上下文,程序從對應的 setjmp 處繼續執行。#include <stdio.h>
#include <setjmp.h>
static jmp_buf buf;
void second(void) {
printf("second\n");
longjmp(buf, 1); // 跳回 setjmp,使其返回 1
}
void first(void) {
second();
printf("first\n"); // 不會執行
}
int main() {
if (!setjmp(buf)) { // 首次返回 0,進入 if
first();
} else { // longjmp 跳回,返回 1,進入 else
printf("main\n");
}
return 0;
}
// 輸出:second / main
直接使用 setjmp/longjmp 容易出錯(如資源洩漏、棧幀破壞)。Except 接口通過宏與函數封裝,提供類似 TRY-EXCEPT、TRY-FINALLY 的結構化異常處理,支持嵌套異常與異常傳播。
typedef struct T { const char *reason; } Except_T; // 異常類型,僅含描述字符串
typedef struct Except_Frame Except_Frame;
struct Except_Frame {
Except_Frame *prev; // 指向上一層幀
jmp_buf env; // setjmp/longjmp 緩衝區
const char *file; // 異常拋出文件
int line; // 異常拋出行號
const Except_T *exception; // 異常對象地址
};
extern Except_Frame *Except_stack; // 異常幀棧頂指針
// 狀態枚舉
enum { Except_entered=0, Except_raised, Except_handled, Except_finalized };
#define TRY do { \
volatile int Except_flag; \
Except_Frame Except_frame; \
Except_frame.prev = Except_stack; \
Except_stack = &Except_frame; \
Except_flag = setjmp(Except_frame.env); \
if (Except_flag == Except_entered) {
#define EXCEPT(e) \
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
} else if (Except_frame.exception == &(e)) { \
Except_flag = Except_handled;
#define ELSE \
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
} else { \
Except_flag = Except_handled;
#define FINALLY \
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
} { \
if (Except_flag == Except_entered) \
Except_flag = Except_finalized;
#define END_TRY \
if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
} if (Except_flag == Except_raised) RERAISE; \
} while (0)
#define RAISE(e) Except_raise(&(e), __FILE__, __LINE__)
#define RERAISE Except_raise(Except_frame.exception, Except_frame.file, Except_frame.line)
#define RETURN switch (Except_stack = Except_stack->prev, 0) default: return
TRY:壓入新幀,調用 setjmp,標記狀態為 Except_entered,執行保護代碼 S。RAISE(e):調用 Except_raise,填充當前幀的 exception、file、line,彈出幀,longjmp 回 setjmp,狀態變為 Except_raised。EXCEPT(e):匹配異常類型,若匹配則執行處理代碼,狀態設為 Except_handled。FINALLY:無論是否異常均執行清理代碼,結束後重新拋出原異常(RERAISE)。END_TRY:清理棧幀,若有未處理異常則 RERAISE 繼續向上傳播。Except_T Allocate_Failed = {"Allocation failed"};
void *allocate(unsigned n) {
void *new = malloc(n);
if (new) return new;
RAISE(Allocate_Failed); // 拋出異常
assert(0); // 理論不可達
}
// 客戶端調用
extern Except_T Allocate_Failed;
char *buf;
TRY
buf = allocate(4096);
EXCEPT(Allocate_Failed)
fprintf(stderr, "couldn't allocate the buffer\n");
exit(EXIT_FAILURE);
END_TRY;
void Except_raise(const Except_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);
else fprintf(stderr, " at 0x%p", e);
if (file && line > 0) fprintf(stderr, " raised at %s:%d\n", file, line);
fprintf(stderr, "aborting...\n");
fflush(stderr);
abort();
}
p->exception = e;
p->file = file;
p->line = line;
Except_stack = Except_stack->prev; // 彈出當前幀
longjmp(p->env, Except_raised); // 長跳轉回 setjmp
}
通過 setjmp/longjmp 結合棧幀鏈表,Except 接口在 C 語言中實現了功能完備的結構化異常處理:支持嵌套、異常傳播、FINALLY 清理、以及類型安全的異常匹配。這為大型 C 項目提供了比 goto、錯誤碼返回更優雅、健壯的錯誤處理方案。