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

C 語言實現結構化異常處理:setjmp/longjmp 深度解析與 Except 接口設計

     編輯:C++ 入門

無 try/catch 怎麼辦?用 setjmp/longjmp 打造 C 語言異常機制

C 語言缺乏原生異常處理,但標準庫提供了 <setjmp.h> 中的 setjmp / longjmp,構成了非局部跳轉的基石。本文從原理到實戰,帶你構建一套可嵌套、帶異常對象、支持 FINALLY 的結構化異常框架 Except。

一、基石:setjmp / longjmp 原理

#include 
jmp_buf env;          // 保存寄存器、棧指針、程序計數器等上下文
int setjmp(jmp_buf env);   // 首次調用返回 0;從 longjmp 返回時返回非 0
void longjmp(jmp_buf env, int val); // 觸發非局部跳轉,val 傳回 setjmp

關鍵特性

  • 跨函數跳轉:突破 goto 只能函數內跳轉的限制
  • 上下文保存/恢復:jmp_buf 實為數組結構體,存放關鍵寄存器狀態
  • 雙重返回值語義:setjmp 返回 0 表初始化,非 0 表異常返回

經典範例:模擬 try/catch

static jmp_buf buf;
void second() { printf("second\n"); longjmp(buf, 1); }
void first()  { second(); printf("first\n"); } // 不會執行
int main() {
    if (!setjmp(buf)) first();  // try 區塊
    else printf("main\n");      // catch 區塊
    return 0;
}
// 輸出:second → main

二、進階:異常棧框架設計

為了支持嵌套異常、異常對象攜帶信息、FINALLY 清理,需維護一個異常幀鏈表:

typedef struct Except_Frame {
    struct Except_Frame *prev;  // 上一幀指標
    jmp_buf env;                // 跳轉環境
    const char *file;           // 異常拋出文件
    int line;                   // 行號
    const Except_T *exception;  // 異常對象指標
} Except_Frame;

Except_Frame *Except_stack = NULL; // 全局/線程局部棧頂

三、Except 接口定義

// except.h
#ifndef EXCEPT_INCLUDED
#define EXCEPT_INCLUDED
#include 
#define T Except_T
typedef struct T { const char *reason; } T; // 異常類型:僅攜帶原因字串

// 拋出異常
#define RAISE(e) Except_raise(&(e), __FILE__, __LINE__)
void Except_raise(const T *e, const char *file, int line);

// 捕獲語法(宏實現)
#define TRY do { ... } while(0)
#define EXCEPT(e) ...
#define ELSE ...
#define FINALLY ...
#define END_TRY ...
#define RERAISE Except_raise(Except_frame.exception, Except_frame.file, Except_frame.line)
#define RETURN switch (Except_stack = Except_stack->prev, 0) default: return
#endif

四、宏展開邏輯(核心實現)

TRY 壓入新幀並調用 setjmp;EXCEPT 比對異常類型;FINALLY 無條件執行清理並重拋異常;END_TRY 彈出幀,若異常未處理則 RERAISE。

// 關鍵狀態枚舉
enum { Except_entered=0, Except_raised, Except_handled, Except_finalized };

五、核心函數實現

// except.c
#ifdef WIN32
__declspec(thread) // 線程局部存儲,支持多線程
#endif
Except_Frame *Except_stack = NULL;

void Except_raise(const 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);
        if (file && line > 0) fprintf(stderr, " at %s:%d", file, line);
        fprintf(stderr, "\naborting...\n");
        fflush(stderr);
        abort();
    }
    p->exception = e;
    p->file = file;
    p->line = line;
    Except_stack = p->prev; // 彈出當前幀
    longjmp(p->env, Except_raised); // 跳回 TRY 的 setjmp
}

六、斷言與實戰建議

  • assert(condition):僅在 Debug 構建生效,釋出版本自動消失,用於檢查「不可能為假」的前置條件(如除數非零)
  • 異常機制用於「預期內的錯誤」(資源耗盡、參數無效),斷言用於「程式邏輯錯誤」(必須修復的 Bug)
  • 線程安全:Windows 用 __declspec(thread),POSIX 用 pthread_key_create 實現 Except_stack 線程隔離

七、參考文獻

《C Interfaces and Implementations: Techniques for Creating Reusable Software》— David R. Hanson,第 4 章「Exceptions」

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