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

C語言異常處理機制:setjmp/longjmp 實現與 Except 接口設計

     編輯:關於C

C 語言的錯誤類型與異常處理需求

程序運行中常見三類錯誤:用戶輸入錯誤、運行期錯誤(如內存不足)、以及異常情況(如除零、非法訪存)。C 語言本身缺乏結構化異常處理機制,標準庫提供的 setjmp 和 longjmp 成為實現非局部跳轉、模擬異常處理的基石。

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)
  • jmp_buf:一個數組類型(通常定義為結構體數組),用於保存程序計數器、棧指針、幀指針、關鍵寄存器等上下文信息。
  • setjmp:建立「跳轉點」,返回 0 表示正常執行;被 longjmp 跳回時返回非零值(即 val)。
  • longjmp:執行「長跳轉」,恢復 env 保存的上下文,程序從對應的 setjmp 處繼續執行。

簡單示例:模擬 try-catch

#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

進階:構建結構化異常接口 Except

直接使用 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

異常拋出與捕獲流程

  1. TRY:壓入新幀,調用 setjmp,標記狀態為 Except_entered,執行保護代碼 S。
  2. RAISE(e):調用 Except_raise,填充當前幀的 exception、file、line,彈出幀,longjmp 回 setjmp,狀態變為 Except_raised。
  3. EXCEPT(e):匹配異常類型,若匹配則執行處理代碼,狀態設為 Except_handled。
  4. FINALLY:無論是否異常均執行清理代碼,結束後重新拋出原異常(RERAISE)。
  5. 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;

Except_raise 核心實現

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、錯誤碼返回更優雅、健壯的錯誤處理方案。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved