在介紹C++編譯器如何實現異常處理的問題之前,先讓大家了解下什麼是C++編譯器?其實C++編譯器是一個與標准化C++高度兼容的編譯環境,不同的編譯器也會對不同的CPU進行不同的優化。
本文討論了C++編譯器如何實現異常處理。我將假定你已經熟悉異常處理的語法和機制,用於VC++的異常處理庫,要用庫中的處理程序替換掉VC++提供的那個,你只需要調用下面這個函數:
- install_my_handler();
之後,程序中的所有異常,從它們被拋出到堆棧展開stack unwinding),再到調用catch塊,最後到程序恢復正常運行,都將由我的異常處理庫來管理。
與其它C++特性一樣,C++標准並沒有規定編譯器應該如何來實現異常處理。這意味著每一個編譯器的提供商都可以用它們認為恰當的方式來實現它。下面我會 描述一下VC++是怎麼做的,但即使你使用其它的編譯器或操作系統①,本文也應該會是一篇很好的學習材料。VC++的實現方式是以windows系統的結 構化異常處理SEH)②為基礎的。
我認為C++編譯器異常或者是被明確的拋出的,或者是由於除零溢出、空指針訪問等引起的。當它發生時會產生一個中斷,接下來控制權就會傳遞到操作系統 的手中。操作系統將調用異常處理程序,檢查從異常發生位置開始的函數調用序列,進行堆棧展開和控制權轉移。Windows定義了結構 "EXCEPTION_REGISTRATION",使我們能夠向操作系統注冊自己的異常處理程序。
- struct EXCEPTION_REGISTRATION
- {
- EXCEPTION_REGISTRATION* prev;
- DWORD handler;
- };
注冊時,只需要創建這樣一個結構,然後把它的地址放到FS段偏移0的位置上去就行了。下面這句匯編代碼演示了這一操作:
- mov FS:[0], exc_regp
prev字段用於建立一個EXCEPTION_REGISTRATION結構的鏈表,每次注冊新的EXCEPTION_REGISTRATION時,我們都要把原來注冊的那個的地址存到prev中。那麼,那個異常回調函數長什麼樣呢?在excpt.h中,windows定義了它的原形:
- EXCEPTION_DISPOSITION (*handler)(
- _EXCEPTION_RECORD *ExcRecord,
- void* EstablisherFrame,
- _CONTEXT *ContextRecord,
- void* DispatcherContext);
不要管它的參數和返回值,我們先來看一個簡單的例子。下面的程序注冊了一個C++編譯器異常處理程序,然後通過除以零產生了一個異常。異常處理程序捕獲了它,打印了一條消息就完事大吉並退出了。
- #include
- #include
- using std::cout;
- using std::endl;
- struct EXCEPTION_REGISTRATION
- {
- EXCEPTION_REGISTRATION* prev;
- DWORD handler;
- };
- EXCEPTION_DISPOSITION myHandler(
- _EXCEPTION_RECORD *ExcRecord,
- void * EstablisherFrame,
- _CONTEXT *ContextRecord,
- void * DispatcherContext)
- {
- cout << "In the exception handler" << endl;
- cout << "Just a demo. exiting..." << endl;
- exit(0);
- return ExceptionContinueExecution; //不會運行到這
- }
- int g_div = 0;
- void bar()
- {
- //初始化一個EXCEPTION_REGISTRATION結構
- 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_div; //異常,除零溢出
- }
- int main()
- {
- bar();
- return 0;
- }
注意EXCEPTION_REGISTRATION必須定義在棧上,並且必須位於比上一個結點更低的內存地址上,windows對此有嚴格要求,達不到的話,它就會立刻終止進程。