C++ 異常處理將正常流程與錯誤處理分離,但標準未規定實作細節。本文深入剖析 VC++ 如何在 Windows 結構化異常處理 (SEH) 基礎上實現 C++ 異常機制,涵蓋堆疊幀佈局、EXCEPTION_REGISTRATION 鏈表、funcinfo/unwind table 資料結構、堆疊展開、Catch 區塊調用、多執行緒支援,並提供可替換 VC++ 預設處理器的自訂異常處理庫原始碼。
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);
FS:[0] 指向鏈首ExceptionContinueSearch 繼續下一個struct EXCEPTION_REGISTRATION_VCPP {
EXCEPTION_REGISTRATION_VCPP* prev;
DWORD handler; // 指向 __CxxFrameHandler
int id; // 當前程式碼區域 ID (try 區塊/物件生命週期)
DWORD ebp; // 與堆疊幀指標 EBP 重疊
};
id 欄位追蹤執行位置:try 區塊入口設 start_id,物件建構後設物件唯一 IDstruct FuncInfo {
int magic; // 魔數識別
int maxState; // 最大狀態數
UnwindMapEntry* unwindMap; // 堆疊展開表
TryBlockMapEntry* tryBlockMap; // try 區塊表
CatchHandlerMapEntry* catchHandlerMap; // catch 處理器表
// ... 其他欄位
};

_CxxThrowException(&exception, &excpt_info) → RaiseException(0xE06D7363, ...)_EXCEPTION_RECORD,ExceptionInformation[0]=異常位址, [1]=excpt_info 位址FS:[0] 遍歷 SEH 鏈,調用各層 __CxxFrameHandler__CxxFrameHandler 檢查 FuncInfo:
id 在某 try 區間 → 遍歷對應 catch 表type_info:完全相同或捕獲基類捕獲派生類 (透過 etypeinfo_table)struct UnwindMapEntry {
int prevState; // 前一狀態索引 (-1 表結束)
void (*cleanupFunc)(); // 清理函數入口 (呼叫解構子/釋放記憶體)
};
new 操作) 對應一個 entryprevState 形成鏈表id → 索引 entry → 調用 cleanupFunc → 追蹤 prevState 直到 -1void foo() {
T o1, o2;
{ T o3; } // o3 離開作用域,id 恢復
10/g_i; // 異常拋出點:僅 o1, o2 存活
T o4; // 未建構
}
// 編譯器在 o1/o2 建構後設 id,析構前恢復 id
// unwind table 對應 o1/o2 的 cleanupFunc 呼叫其解構子
T* p = new T():配置記憶體 → 建構函數new 分配 ID,unwind table 含釋放記憶體的 cleanupFuncexception_storage (TLS),註冊 catch_block_protectorthrow;):ExceptionInformation[1],[2] = 0,protector 恢復原異常繼續傳播exception_helper::destroy),新異常正常傳播// 每個執行緒獨立的 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++ 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_REGISTRATION | SEH 鏈表節點 | prev, handler, id, ebp |
| FuncInfo | 函數異常中繼資料 | unwindMap, tryBlockMap, catchHandlerMap |
| UnwindMapEntry | 堆疊展開項目 | prevState, cleanupFunc |
| excpt_info | 異常物件描述 | type_info, copyCtor, dtor, size |
| type_info | RTTI 型別資訊 | name(), operator==(), before() |
C++ 異常處理是編譯器、執行期庫與作業系統協同運作的精密機制。理解其內部運作,不僅能解決疑難雜症 (跨 DLL 異常、效能調優、混合模式除錯),更能深化對程式語言實作模型的認識。現代 C++ 趨向零開銷異常模型與標準化工具 (std::exception_ptr 等),但核心原理——堆疊展開、型別匹配、資源安全釋放——始終不變。