動機:C 無 try/catch,如何實現跨層級錯誤跳轉?
- 深層嵌套函數發現嚴重錯誤(資源耗盡、檔案損毀)
- 需直接跳回主流程錯誤處理點,略過中間層級
setjmp/longjmp 提供「非局部 goto」能力
核心 API
#include
int setjmp(jmp_buf env); // 保存當前堆疊幀環境 → env,首次返回 0
void longjmp(jmp_buf env, int val); // 恢復 env 環境,setjmp 返回 val (≠0)
完整範例:模擬 try/catch/finally
#include
#include
#include
jmp_buf exception_env; // 全域/靜態環境緩衝區
/* 異常代碼 */
#define EXC_DIV_ZERO 1
#define EXC_OOM 2
#define EXC_IO_ERROR 3
/* 拋出異常宏 */
#define THROW(code) longjmp(exception_env, code)
/* 捕獲區塊宏 */
#define TRY if (setjmp(exception_env) == 0)
#define CATCH(code) else if (exception_code == code)
#define FINALLY else
int exception_code; // longjmp 傳入的值
/* 可能拋出異常的深層函數 */
double safe_div(double a, double b) {
if (b == 0.0) THROW(EXC_DIV_ZERO);
return a / b;
}
void allocate_big(int n) {
void *p = malloc(n);
if (!p) THROW(EXC_OOM);
free(p);
}
int main() {
TRY {
printf("進入保護區塊\n");
double r = safe_div(10.0, 0.0); // 觸發除零
printf("不會執行到這裡\n");
}
CATCH(EXC_DIV_ZERO) {
printf("捕獲: 除零錯誤\n");
}
CATCH(EXC_OOM) {
printf("捕獲: 記憶體不足\n");
}
FINALLY {
printf("其他異常或無異常\n");
}
printf("程式繼續執行...\n");
return 0;
}
執行輸出
進入保護區塊
捕獲: 除零錯誤
程式繼續執行...
關鍵規則與限制(必讀)
| 規則 | 說明 |
setjmp 僅能在特定語境 | 控制流語句條件、比較運算、獨立語句、參數傳遞。不可用於複雜運算式中 |
longjmp 不呼叫解構子 | C++ 物件、RAII 資源不會自動釋放 → 洩漏風險 |
| 跳過的函數不會返回 | 中間函數的 return、finally 邏輯完全跳過 |
jmp_buf 生命週期 | 必須在 longjmp 時仍有效 (全域/靜態/上層堆疊) |
longjmp val ≠ 0 | 若傳 0,setjmp 會返回 1 (避免歧義) |
| 訊號處理器中安全 | 僅 setjmp/longjmp、signal 等少數函數為訊號安全 |
進階:模擬 finally (資源清理)
C 無 finally,需手動在 longjmp 前清理,或使用 GNU C 延伸 __attribute__((cleanup)):
/* GCC/Clang 清理屬性 */
void cleanup_file(void *p) { FILE **f = p; if (*f) fclose(*f); }
#define AUTO_CLEANUP(type, var, cleanup_fn) \
type var __attribute__((cleanup(cleanup_fn)))
void demo() {
AUTO_CLEANUP(FILE*, f, cleanup_file) = fopen("data.txt", "r");
if (!f) THROW(EXC_IO_ERROR);
// 若此處 longjmp,cleanup_file 會自動被呼叫
}
實際應用場景
- 解析器錯誤恢復:跳出深層遞迴下降
- 嵌入式系統:嚴重錯誤重啟任務
- 庫與應用邊界:統一錯誤入口
- 協程/綠色執行緒實現核心原語
對比:C++ Exception vs setjmp/longjmp
| 特性 | C++ Exception | setjmp/longjmp |
| 自動解構子 | ✅ | ❌ |
| 型別安全 | ✅ | ❌ (int 代碼) |
| 堆疊展開 | ✅ | ❌ (直接跳轉) |
| 效能 (無異常路徑) | 零開銷 | setjmp 保存環境有開銷 |
| 可移植性 | 標準 C++ | 標準 C |
最佳實踐建議
- 極度謹慎使用:優先考慮錯誤碼回傳、
errno、結果型別
- 文檔標註:函數可能
longjmp 的異常代碼
- 資源管理:跳轉前手動釋放,或用 cleanup 屬性
- 不可跨越 C++ 物件邊界:混合編譯極其危險
- 單一
jmp_buf 對應單一保護域:巢狀需棧式管理
- 上一篇文章: 三數排序演算法對決:暴力枚舉、三輪交換、極值法與倒三角列印實戰
- 下一篇文章: C 預處理器技巧:#if 0 代碼塊註釋與條件編譯最佳實踐
Copyright ©
程式碼之家 All Rights Reserved