C++ 的異常處理機制是相對傳統錯誤處理的一大創新,它將正常邏輯與錯誤處理清晰分離,使程式碼更整潔、易維護。本文探討編譯器如何實現異常處理,假設讀者已熟悉異常語法。文中亦提供一個自定義的 VC++ 異常處理庫,調用 install_my_handler() 即可替換預設處理器,接管從拋出、堆疊展開到 catch 區塊的完整流程。
C++ 標準未規定編譯器必須如何實現異常處理,各廠商可自行設計。本文以 Visual C++ 為例,其實現基於 Windows 結構化異常處理(SEH)。即使使用其他編譯器或作業系統,本文仍具參考價值。
異常可能由明確拋出或硬體錯誤(除零、空指標)觸發,產生中斷,控制權移交作業系統。OS 調用異常處理器,檢查呼叫鏈,執行堆疊展開與控制轉移。Windows 定義 EXCEPTION_REGISTRATION 結構供註冊自定義處理器:
struct EXCEPTION_REGISTRATION {
EXCEPTION_REGISTRATION* prev;
DWORD handler;
};
註冊時,建立此結構並將其位址寫入 FS:[0]。prev 欄位串聯舊的註冊記錄,形成鏈表。異常回調函數原型定義於 excpt.h:
EXCEPTION_DISPOSITION (*handler)(
_EXCEPTION_RECORD *ExcRecord,
void* EstablisherFrame,
_CONTEXT *ContextRecord,
void* DispatcherContext);
下列程式註冊處理器,製造除零異常,處理器捕獲後列印訊息並退出:
#include <iostream>
#include <windows.h>
using std::cout; using std::endl;
struct EXCEPTION_REGISTRATION { EXCEPTION_REGISTRATION* prev; DWORD handler; };
EXCEPTION_DISPOSITION myHandler(_EXCEPTION_RECORD*, void*, _CONTEXT*, void*) {
cout << "In the exception handler" << endl;
cout << "Just a demo. exiting..." << endl;
exit(0);
return ExceptionContinueExecution;
}
int g_p = 0;
void bar() {
EXCEPTION_REGISTRATION reg, *preg = ®
reg.handler = (DWORD)myHandler;
DWORD prev;
_asm { mov EAX, FS:[0]; mov prev, EAX }
reg.prev = (EXCEPTION_REGISTRATION*)prev;
_asm { mov EAX, preg; mov FS:[0], EAX }
int j = 10 / g_p; // 觸發除零異常
}
int main() { bar(); return 0; }
輸出: In the exception handler / Just a demo. exiting...
注意:EXCEPTION_REGISTRATION 必須分配在堆疊上,且位址低於前一節點,否則 Windows 會終止進程。
堆疊保存區域物件,每個函數擁有堆疊幀。編譯器常將熱門物件放入暫存器優化存取。堆疊向下增長(新項目位址較低)。下圖展示 foo → bar → widget 呼叫時的堆疊佈局:

理解堆疊結構是掌握異常處理中堆疊展開機制的基礎。