目錄
1 try-except
2 try-finally語句
3 例子
4 與Windows異常處理機制的關係
5 參考文獻
try-except[編輯]
__try { // 受保護執行的代碼 } __except ( 過濾表達式 ) { // 異常處理代碼 }
首先,__try複合語句中的受保護的代碼被執行。如果沒有異常發生,則繼續執行__except複合語句之後的代碼。如果__try複合語句中的受保護執行的代碼發生了異常,或受保護執行的代碼調用的函式內部發生了異常並要求調用者來處理該異常,__except語句的過濾表達式(filter expression)被求值,根據其結果來決定如何處理異常:
EXCEPTION_CONTINUE_EXECUTION (–1) : 導致異常的問題已經解決,在異常出現的現場重新執行操作。
EXCEPTION_CONTINUE_SEARCH (0) :當前__except語句不能處理該異常,通知作業系統繼續搜尋該執行緒其他的異常處理程式。
EXCEPTION_EXECUTE_HANDLER (1):當前__except語句識別該異常,通過執行__except的複合語句來處理該異常。然後執行__except複合語句之後的代碼。
內在函式GetExceptionCode返回一個32位整型值,表示異常的類型。內在函式GetExceptionInformation返回異常的詳細信息及現場信息(如CPU暫存器的值) 。這兩個函式可用於異常表達式中來判斷是否處理該異常。這裡說的內在函式(intrinsic function),是指編譯器提供內聯(inline)實現的函式。
Windows作業系統的應用程式的main()函式受到結構化異常的try-except語句保護,因此程式的未被處理的異常都會被捕獲。這是因為,Windows作業系統在載入用戶進程時,Kernel32.dll中的BaseProcessStart函式在__try塊中調用了用戶進程的入口函式mainCRTStartup,因此用戶程式的所有異常都會被捕獲、得到處理。
可以使用C++運行時庫中的_set_se_translator()函式把結構化異常轉為拋出一個C++對象的C++異常。此外,C++異常的catch(...)語句也能直接捕捉結構化異常。
可以使用作業系統運行時庫kernel32中的SetUnhandledExceptionFilter()函式設定頂層未處理異常過濾器(top-level unhandled exception filter),捕獲進程的各個執行緒中一切未被處理的結構化異常。該函式一般用於從特定的錯誤中恢復,如無效的調用棧(invalid stack)。
try-finally語句[編輯]
__try { // 受保護執行的代碼 } __finally { // 清理用途的代碼 }
__try複合語句中受保護的代碼以任何方式執行結束後,不論__try複合語句是因為出現異常而非正常結束,還是沒有出現異常而正常結束,,__finally複合語句中的代碼都會被執行。
如果__try複合語句中受保護的代碼的執行沒有出現異常,包括用goto語句或longjump系統函式跳出__try複合語句等情形,這時將執行__finally複合語句,然後執行try-finally語句之後的其他代碼。
如果__try複合語句中受保護的代碼執行中出現了異常,首先按函式調用順序從新向舊搜尋所有包含了出現異常的執行點的try-except語句,執行每個except的過濾函式,直到某個try-except語句的過濾函式結果值為EXCEPTION_EXECUTE_HANDLER。這時,再從包含異常出現的執行點的最內層try-finally語句開始由內向外執行每個finally塊中的代碼,直至回退到那個過濾函式結果值為EXCEPTION_EXECUTE_HANDLER的try-except語句執行其except塊,最後執行該try-except語句後面的其他代碼。
在__finally複合語句中使用內在函式AbnormalTermination判斷是正常結束還是非正常結束__try複合語句。
例子[編輯]
#include <stdio.h> #include <windows.h> // for EXCEPTION_ACCESS_VIOLATION #include <excpt.h> int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep) { printf("在異常表達式中\n"); if (code == EXCEPTION_ACCESS_VIOLATION) { printf("接受處理訪問違例異常\n"); return EXCEPTION_EXECUTE_HANDLER; } else { printf("其他異常都不處理\n"); return EXCEPTION_CONTINUE_SEARCH; }; } int main() { int* p = 0x00000000; // pointer to NULL printf("開始主程式\n"); __try{ printf("進入外層的try\n"); __try{printf("進入內層的try");int *p=0; // 空指針*p = 13; // 導致訪問衝突異常 }__finally{printf("在finally內部。");printf(AbnormalTermination() ? "正常終止\n" : "非正常終止\n"); } }__except(filter(GetExceptionCode(), GetExceptionInformation())){ printf("在except內部\n"); } printf("主函式結束\n"); }
與Windows異常處理機制的關係[編輯]
Windows作業系統(自
Windows95起),對每個用戶執行緒,都設立一個異常處理幀鍊表來處理異常事件。該鍊表的每個異常處理幀由兩個成員組成,分別是鍊表上一項地址、當前異常處理器地址,組成了結構_EXCEPTION_REGISTRATION_RECORD。異常處理器是指一個處理異常的回調函式(callback function)。執行緒信息塊(thread information block)的開始處(即FS:[0]指向的記憶體,FS是CPU的一個段暫存器)保存了異常處理幀鍊表的表頭項的地址。程式執行遇到異常事件而中斷時,作業系統的RtlDispatchException函式會從FS:[0]指向的鍊表表頭依次調用每個節點包含異常處理回調函式,直到某個異常處理回調函式的返回值為0表示已經處理該異常,該執行緒可以恢復執行。鍊表最末一項是作業系統在裝入執行緒時設定的指向kernel32!UnhandledExceptionFilter函式,該函式總是向用戶顯示“Application error”對話框。
上述異常處理器程式及鍊表,是由用戶程式自己安裝的。鍊表各節點保存在程式調用棧(call stack)上。
Windows異常處理機制支持嵌套異常的處理,即在執行異常處理回調函式時再次發生異常。這種情況下仍遵照普通異常處理機制,作業系統RtlDispatchException函式
再入處理新出現的嵌套的異常。嵌套的異常的處理函式得到的DispatcherContext參數值即為在執行時發生了新異常的異常幀的地址。
各種程式語言基於上述Windows異常處理機制,設計了各自的異常處理語句控制結構。Microsoft擴展了C語言語法,設計了結構化異常處理的try-except與try-finally語句。一個函式的所有在函式的的try-except與try-finally形成了一個基於包含(enclosing)關係的森林 (數據結構)。一個函式內如果有__try語句,則在函式的入口與結尾處,編譯器插入了EH_prolog與EH_epilog代碼,把函式內所有在try塊中被保護的代碼包了起來。 EH_prolog在調用棧上創建一個_EXCEPTION_REGISTRATION_RECORD,作為異常處理鍊表的新的表頭,其中包含了Visual C++ 的運行時庫msvcrt.dll的__except_handler4函式地址。在函式塊的結尾處,EH_epilog把這項_EXCEPTION_REGISTRATION_RECORD從鍊表頭移除,恢復其原來的表頭。__except語句中的過濾表達式,由掛在鍊表中的異常處理回調函式MSVCR100D!__except_handler4來調用執行,返回值即為過濾表達式的求值結果。
實際上,編譯器實現結構化異常時,把鍊表每項的數據結構由2個成員擴展為5個成員,即在高地址方向追加了一個scopetable_entries類型結構體數組的指針、一個整型項表示執行點位於當前函式的哪個try塊中、一個保存暫存器EBP的整數項。此後(低地址方向)緊接著是一個指向EXCEPTION_POINTERS結構的指針(前述的內在函式GetExceptionInformation即返回這個指針值)。
異常發生時,作業系統的異常處理機制的ntdll!RtlDispatchException函式會從FS:[0]指向的鍊表表頭依次調用異常幀鍊表的每個節點所包含的異常處理回調函式MSVCR100D!__except_handler4,根據該回調函式的返回值來確定異常是否已經被處理,可以根據異常上下文(Exception context)恢複線程的執行。__except_handler4回調函式實際上只是調用了MSVCR100D!__except_handler4_common函式。__except_handler4_common函式是實際的workhorse,負責在當前異常幀所在的函式中查找那個try-except語句能夠處理該異常(即過濾表達式的結果為1) 。如果不存在這樣的try-except塊,__except_handler4_common函式返回ExceptionContinueExecution(值0),由RtlDispatchException繼續訪問異常幀鍊表的下一個節點。如果找到了能處理當前異常的try-except塊,__except_handler4_common函式首先調用全局展開函式_EH4_GlobalUnwind,把從異常幀鍊表表頭所在的函式,直到能處理當前異常的函式的下一層函式,都做棧展開(unwinding);然後,對能處理當前異常的函式,從包含執行點的最記憶體__try語句,直到能處理異常的try-except塊,調用局部展開函式_EH4_LocalUnwind;再執行能處理異常的try-except塊的異常處理代碼;最後,繼續執行try-except之後的其他代碼。
全局展開,是由_EH4_GlobalUnwind調用ntdll!RtlUnwind,RtlUnwind遍歷訪問異常幀鍊表,把從表頭幀到目標幀(不含)的所有異常處理回調函式用異常碼(STATUS_UNWIND 即0C0000027H)、異常標誌(EXCEPTION_UNWINDING即值2)調用。異常處理回調函式根據當前的異常碼與異常標誌,對當前異常幀所在函式,從包含了產生異常的執行點的最內層__try語句開始,直至函式內的最外層try塊,依次調用try-finally的清理用途代碼。這一步實際上是調用_EH4_LocalUnwind函式來完成。
局部展開,由_EH4_LocalUnwind函式實現,是從包含了產生異常的執行點的最內層__try語句開始,按著代碼的包含關係向外直至目標try-except語句為止,依次調用try-finally的清理用途代碼。