首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言異常處理機制:setjmp/longjmp 跨函數非局部跳轉完整教學

     編輯:關於C語言

動機: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

最佳實踐建議

  1. 極度謹慎使用:優先考慮錯誤碼回傳、errno、結果型別
  2. 文檔標註:函數可能 longjmp 的異常代碼
  3. 資源管理:跳轉前手動釋放,或用 cleanup 屬性
  4. 不可跨越 C++ 物件邊界:混合編譯極其危險
  5. 單一 jmp_buf 對應單一保護域:巢狀需棧式管理
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
特性C++ Exceptionsetjmp/longjmp
自動解構子✅❌
型別安全✅❌ (int 代碼)
堆疊展開✅❌ (直接跳轉)
效能 (無異常路徑)零開銷setjmp 保存環境有開銷
可移植性標準 C++標準 C