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

C++ 編譯器如何實現異常處理:基於 Windows SEH 的深度剖析

     編輯:關於C++

C++ 異常處理將正常流程與錯誤處理分離,但標準未規定實作細節。本文深入剖析 VC++ 如何在 Windows 結構化異常處理 (SEH) 基礎上實現 C++ 異常機制,涵蓋堆疊幀佈局、EXCEPTION_REGISTRATION 鏈表、funcinfo/unwind table 資料結構、堆疊展開、Catch 區塊調用、多執行緒支援,並提供可替換 VC++ 預設處理器的自訂異常處理庫原始碼。

一、Windows SEH 基礎

struct EXCEPTION_REGISTRATION {
    EXCEPTION_REGISTRATION* prev; // 鏈表前驅
    DWORD handler;                // 回調函數位址
};
// 註冊:mov FS:[0], reg_addr
// 回調原型:
EXCEPTION_DISPOSITION handler(
    _EXCEPTION_RECORD* ExcRecord,
    void* EstablisherFrame,
    _CONTEXT* ContextRecord,
    void* DispatcherContext);
  • 每個執行緒擁有獨立 SEH 鏈表,FS:[0] 指向鏈首
  • 異常發生 → OS 捕獲 → 遍歷鏈表調用 handler → ExceptionContinueSearch 繼續下一個

二、VC++ 擴充:支援 C++ 異常

擴充的註冊結構

struct EXCEPTION_REGISTRATION_VCPP {
    EXCEPTION_REGISTRATION_VCPP* prev;
    DWORD handler;          // 指向 __CxxFrameHandler
    int id;                 // 當前程式碼區域 ID (try 區塊/物件生命週期)
    DWORD ebp;              // 與堆疊幀指標 EBP 重疊
};
  • 幾乎每個函數序言建立此結構並註冊,尾聲恢復上一層
  • id 欄位追蹤執行位置:try 區塊入口設 start_id,物件建構後設物件唯一 ID

funcinfo 結構:函數異常中繼資料

struct FuncInfo {
    int magic;              // 魔數識別
    int maxState;           // 最大狀態數
    UnwindMapEntry* unwindMap; // 堆疊展開表
    TryBlockMapEntry* tryBlockMap; // try 區塊表
    CatchHandlerMapEntry* catchHandlerMap; // catch 處理器表
    // ... 其他欄位
};

三、運行時堆疊與異常傳播

Runtime stack with exception handling

  1. 拋出異常:_CxxThrowException(&exception, &excpt_info) → RaiseException(0xE06D7363, ...)
  2. OS 封裝 _EXCEPTION_RECORD,ExceptionInformation[0]=異常位址, [1]=excpt_info 位址
  3. 從 FS:[0] 遍歷 SEH 鏈,調用各層 __CxxFrameHandler
  4. __CxxFrameHandler 檢查 FuncInfo:
    • 若當前 id 在某 try 區間 → 遍歷對應 catch 表
    • 比較 type_info:完全相同或捕獲基類捕獲派生類 (透過 etypeinfo_table)
  5. 找到匹配 catch → 堆疊展開 → 複製異常到 catch 參數 → 調用 catch → 取得返回位址 → 清理所有幀 → 跳轉返回位址

四、堆疊展開機制

Unwind Table (堆疊展開表)

struct UnwindMapEntry {
    int prevState;          // 前一狀態索引 (-1 表結束)
    void (*cleanupFunc)();  // 清理函數入口 (呼叫解構子/釋放記憶體)
};
  • 每個關鍵區域 (try 區塊、物件生命週期、new 操作) 對應一個 entry
  • entries 依程式碼順序排列,prevState 形成鏈表
  • 展開時:讀取當前 id → 索引 entry → 調用 cleanupFunc → 追蹤 prevState 直到 -1

物件生命週期追蹤

void foo() {
    T o1, o2;
    { T o3; } // o3 離開作用域,id 恢復
    10/g_i;   // 異常拋出點:僅 o1, o2 存活
    T o4;     // 未建構
}
// 編譯器在 o1/o2 建構後設 id,析構前恢復 id
// unwind table 對應 o1/o2 的 cleanupFunc 呼叫其解構子

new 運算符展開

  • T* p = new T():配置記憶體 → 建構函數
  • 建構函數拋出異常 → 必須釋放已配置記憶體
  • VC++ 為 new 分配 ID,unwind table 含釋放記憶體的 cleanupFunc

五、Catch 區塊重新拋出與新異常處理

  • 調用 catch 前:儲存當前異常至 exception_storage (TLS),註冊 catch_block_protector
  • Catch 區塊拋出:
    • 重新拋出 (throw;):ExceptionInformation[1],[2] = 0,protector 恢復原異常繼續傳播
    • 新異常:protector 銷毀舊異常 (exception_helper::destroy),新異常正常傳播

六、多執行緒支援:Thread Local Storage (TLS)

// 每個執行緒獨立的 exception_storage
__declspec(dllexport) exception_storage* get_exception_storage() {
    return reinterpret_cast(TlsGetValue(tlsIndex));
}

// DLL 入口點管理 TLS 生命週期
BOOL APIENTRY DllMain(HANDLE, DWORD reason, LPVOID) {
    switch (reason) {
    case DLL_PROCESS_ATTACH:
        tlsIndex = TlsAlloc();
        TlsSetValue(tlsIndex, new exception_storage()); // 主執行緒
        break;
    case DLL_THREAD_ATTACH:
        TlsSetValue(tlsIndex, new exception_storage());
        break;
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        delete get_exception_storage();
        break;
    }
    return TRUE;
}

七、自訂異常處理庫安裝

bool install_my_handler() {
    // 修改 __CxxFrameHandler 入口碼為 JMP my_exc_handler
    // 需 VirtualProtectEx 變更頁面保護為 RW
    // 寫入 5 位元組 JMP 指令 (E9 xx xx xx xx)
}

替換後,所有 C++ 異常由 my_exc_handler 統一處理,可用於偵錯、效能分析、自訂異常語意。

八、VC++ 版本差異與標準演進

版本行為差異
VC++ 6.0先複製異常到 catch 幀,再展開堆疊
VC++ 7.0+先展開堆疊,再複製異常 (符合 Itanium C++ ABI 趨勢)
C++/CLI (VC++ 2005+)托管異常與原生異常互操作,finally 關鍵字
C++11/14/17/20標準化 noexcept、std::exception_ptr、std::current_exception、零開銷異常模型優化

九、核心資料結構總覽

結構用途關鍵欄位
EXCEPTION_REGISTRATIONSEH 鏈表節點prev, handler, id, ebp
FuncInfo函數異常中繼資料unwindMap, tryBlockMap, catchHandlerMap
UnwindMapEntry堆疊展開項目prevState, cleanupFunc
excpt_info異常物件描述type_info, copyCtor, dtor, size
type_infoRTTI 型別資訊name(), operator==(), before()

結語

C++ 異常處理是編譯器、執行期庫與作業系統協同運作的精密機制。理解其內部運作,不僅能解決疑難雜症 (跨 DLL 異常、效能調優、混合模式除錯),更能深化對程式語言實作模型的認識。現代 C++ 趨向零開銷異常模型與標準化工具 (std::exception_ptr 等),但核心原理——堆疊展開、型別匹配、資源安全釋放——始終不變。

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