C 語言無原生 try/catch,但提供了多層次錯誤處理手段。本文由淺入深梳理:防禦性 if、返回碼約定、setjmp/longjmp 非局部跳轉、協程雛形、巨集封裝 TRY/CATCH。
void convert(int a[], int n, int k) {
if (!a || n <= 1 || !(k %= n)) return; // 異常情況直接返回
// ... 正常邏輯
}
#define RT_OK 0
#define RT_EP -1 // 參數錯誤
#define RT_EM -2 // 內存錯誤
int div(double a, double b, double *res) {
if (b == 0 || !res) return RT_EP;
*res = a / b;
return RT_OK;
}
if (div(x,y,&z) != RT_OK) handle_error();。malloc 返回 NULL,fopen 返回 NULL)。<setjmp.h> 提供兩函數:
int setjmp(jmp_buf env):保存當前寄存器/棧幀到 env,**首次返回 0**。void longjmp(jmp_buf env, int val):恢復 env 狀態,**使對應 setjmp 返回 val**(val ≠ 0)。關鍵規則:
longjmp 不能讓 setjmp 返回 0(若 val==0 強制改為 1),否則行為未定義。jmp_buf 內含:EBP, EBX, EDI, ESI, ESP, EIP 等關鍵寄存器(x86),足以恢復執行上下文。volatile 變量值正確,普通局部變量可能被寄存器緩存導致值舊。#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
jmp_buf env;
double safe_div(double a, double b) {
if (b == 0) longjmp(env, -1); // 拋出異常
return a / b;
}
int main() {
switch (setjmp(env)) {
case 0: // try 區塊
printf("10/2 = %f\n", safe_div(10, 2));
printf("5/0 = %f\n", safe_div(5, 0)); // 這裡 longjmp
break;
case -1: // catch 區塊
fprintf(stderr, "Caught division by zero!\n");
break;
default:
fprintf(stderr, "Unknown error\n");
}
return 0;
}
#include <setjmp.h>
#include <stdio.h>
jmp_buf main_ctx, child_ctx;
int i = 0;
void child() {
for (i = 0; i < 5; i++) {
puts("Child loop begin");
if (!setjmp(child_ctx)) longjmp(main_ctx, 1); // 讓出 CPU
puts("Child loop end");
if (!setjmp(child_ctx)) longjmp(main_ctx, 1);
}
}
int main() {
if (!setjmp(main_ctx)) child(); // 首次進入 child
for (i = 0; i < 5; i++) {
puts("Parent");
if (!setjmp(main_ctx)) longjmp(child_ctx, 1); // 切回 child
}
return 0;
}
輸出交替的 Parent / Child loop begin/end,演示用戶態線程切換核心:保存上下文 → 切換棧指針 → 恢復上下文。
#define TRYBEGIN(var) { jmp_buf var; switch(setjmp(var)) { case 0:
#define CATCH(code) break; case code:
#define CATCHBASE() default:
#define TRYEND break; } }
#define THROW(var, code) longjmp(var, code)
使用:
TRYBEGIN(jbuf) {
risky_operation();
} CATCH(ERR_IO) {
handle_io_error();
} CATCHBASE() {
handle_generic();
} TRYEND
| 機制 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| if 返回 | 簡單函數、性能敏感 | 零開銷、直觀 | 錯誤碼傳遞繁瑣 |
| 返回碼約定 | 庫函數、系統調用 | 標準化、可攜帶 | 調用者易忘檢查 |
| setjmp/longjmp | 深層調用異常跳出、協程 | 跨函數跳轉、實現 try/catch | 資源洩漏風險、volatile 限制、不可移植 |
| C++ try/catch | C++ 專案 | 自動棧展開、RAII、類型安全 | 需 C++ 編譯器、二進制體積大 |
setjmp/longjmp 僅用於:極深調用棧的致命錯誤快速退出、用戶態協程/綠色線程實現。setjmp,**所有需在跳轉後保持值的變量必須聲明 volatile**。longjmp 目標函數返回後再次 longjmp 到同一 jmp_buf(棧幀已銷毀)。libcoro, libtask)。