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

C語言異常處理機制:從 if 判斷到 setjmp/longjmp 協程

     編輯:關於C語言

C語言異常處理機制:從 if 判斷到 setjmp/longjmp 協程

C 語言無原生 try/catch,但提供了多層次錯誤處理手段。本文由淺入深梳理:防禦性 if、返回碼約定、setjmp/longjmp 非局部跳轉、協程雛形、巨集封裝 TRY/CATCH。

層級 1:防禦性 if 判斷(最原始)

void convert(int a[], int n, int k) {
    if (!a || n <= 1 || !(k %= n)) return;  // 異常情況直接返回
    // ... 正常邏輯
}
  • 優點:簡單、零開銷、就地處理。
  • 缺點:調用鏈深時錯誤碼層層傳遞繁瑣,業務邏輯與錯誤處理混雜。

層級 2:返回碼約定(庫函數風格)

#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();。
  • C 標準庫多採用此風格(malloc 返回 NULL,fopen 返回 NULL)。

層級 3:setjmp / longjmp 非局部跳轉(核心機制)

<setjmp.h> 提供兩函數:

  • int setjmp(jmp_buf env):保存當前寄存器/棧幀到 env,**首次返回 0**。
  • void longjmp(jmp_buf env, int val):恢復 env 狀態,**使對應 setjmp 返回 val**(val ≠ 0)。

關鍵規則:

  1. longjmp 不能讓 setjmp 返回 0(若 val==0 強制改為 1),否則行為未定義。
  2. jmp_buf 內含:EBP, EBX, EDI, ESI, ESP, EIP 等關鍵寄存器(x86),足以恢復執行上下文。
  3. 跳轉跨越函數棧幀,**中間函數的清理代碼(析構、unlock、free)不會執行**,資源易洩漏。
  4. 僅保證 volatile 變量值正確,普通局部變量可能被寄存器緩存導致值舊。

實例:模擬 try/catch 除零異常

#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;
}

層級 4:協程雛形(setjmp 保存/切換棧幀)

#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,演示用戶態線程切換核心:保存上下文 → 切換棧指針 → 恢復上下文。

層級 5:巨集封裝 TRY/CATCH/THROW

#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/catchC++ 專案自動棧展開、RAII、類型安全需 C++ 編譯器、二進制體積大

最佳實踐建議

  1. 90% 場景用 返回碼 + 防禦性 if 即可,簡單可靠。
  2. setjmp/longjmp 僅用於:極深調用棧的致命錯誤快速退出、用戶態協程/綠色線程實現。
  3. 若用 setjmp,**所有需在跳轉後保持值的變量必須聲明 volatile**。
  4. 嚴禁在 longjmp 目標函數返回後再次 longjmp 到同一 jmp_buf(棧幀已銷毀)。
  5. 現代 C 專案若需異常機制,建議引入 C++ 編譯單元或使用成熟庫(如 libcoro, libtask)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved