最近做了一個Windows下的異常處理模塊,查閱了一些新的資料,結(jié)合我自己的理解,將一些點滴記錄如下,希望對兄弟們有所幫助。
一、C++標(biāo)準(zhǔn)異常
也就是try、throw、catch這三個關(guān)鍵字。
try
{
……
throw <exception-data>
……
}
catch (<exception-declaration 1>)
{
……
}
catch (<exception-declaration 2>)
{
……
}
……
try塊中的throw會拋出一個數(shù)據(jù)<exception-data>,比如一個整數(shù),一個字串,或是其他自定義類型的數(shù)據(jù)。這時,當(dāng)前程序中止執(zhí)行,開始查找catch入口。throw拋出的數(shù)據(jù)類型與catch入口的<exception-declaration>數(shù)據(jù)類型必須匹配,這一點類似函數(shù)調(diào)用的形參、實參匹配。一個try塊可以對應(yīng)多個catch塊,這一點類似于函數(shù)的重載。當(dāng)然,你也可以用catch (…)來接收所有可能拋出的數(shù)據(jù)。MFC提供了一些標(biāo)準(zhǔn)的拋出異常類型,如CFileException類、CDaoException類等,它們都是CException類的派生類,使用MFC時可以了解一下,這里就不多說。
執(zhí)行完catch塊,程序會繼續(xù)向下執(zhí)行。
當(dāng)throw在本函數(shù)(或說棧的本幀)沒有找到合適的catch塊時,會向上一層調(diào)用函數(shù)(或說棧的上一幀)回溯,直到匹配到合適的try-catch塊為止。也就是說,try-catch塊可以捕獲到try塊中調(diào)用(可以是多層調(diào)用)的函數(shù)中的,沒被處理的異常。同時,try-catch塊是可以嵌套的。
那么,有一個問題:沒有try-catch塊的,或查找到調(diào)用頂層(如main函數(shù))都沒有匹配上catch塊的throw語句會如何執(zhí)行呢?這在不同操作系統(tǒng)會有不同的處理,在Windows中則是由一個叫SEH的機制來處理的。
二、Windows SEH
SEH(Structured Exception Handling),即結(jié)構(gòu)化異常處理,是Microsoft提供的異常處理機制。要了解這個機制,咱先來了解一下__try-__except關(guān)鍵字。
1. __try-__except關(guān)鍵字
__try
{
……
}
__except (<exception>)
{
……
}
__try-__except是Microsoft擴展出的C++關(guān)鍵字,__try塊中出現(xiàn)錯誤或異常,一般不再用throw拋出,而是直接產(chǎn)生一個EXCEPTION_POINTERS類型的異常數(shù)據(jù),然后開始查找SEH例程入口(調(diào)試的情況除外)。首先就會找到與__try塊對應(yīng)的__except塊。__except的參數(shù)<exception>與catch的參數(shù)作用完全不同,也不類似于函數(shù)的參數(shù),它主要是用于控制后面的程序執(zhí)行,為這幾個值之一:
EXCEPTION_EXECUTE_HANDLER(1),表示下面執(zhí)行__except塊內(nèi)及其后面的代碼
EXCEPTION_CONTINUE_EXECUTION(-1),表示回到拋出異常處繼續(xù)向下執(zhí)行
EXCEPTION_CONTINUE_SEARCH(0), 表示查找下一個異常處理例程入口
Microsoft提供兩個函數(shù)GetExceptionCode(), GetExceptionInformation(),分別可以獲取異常號和EXCEPTION_POINTERS類型的異常數(shù)據(jù)指針。而且這兩個函數(shù)只能在__except參數(shù)<exception>的表達式中使用。為了保證這一點,在Microsoft Visual C++(以下簡稱VC)中,編譯器做了特殊處理,如果這兩個函數(shù)沒有在正確的位置,將產(chǎn)生編譯錯誤。(這個感覺有點搞。)
所以,__except一行一般會這樣寫:__except (ExceptFilterFunc(GetExceptionInformation())),其中ExceptFilterFunc是一個自定義的異常處理例程,它輸入一個EXCEPTION_POINTERS *類型的參數(shù),返回EXCEPTION_EXECUTE_HANDLER、EXCEPTION_CONTINUE_EXECUTION或EXCEPTION_CONTINUE_SEARCH。
(注:下面所提到的“異常處理例程”,不管是自定義的還是系統(tǒng)提供的,都是這種類型的函數(shù),這種函數(shù)指針類型在winbase.h中被定義為LPTOP_LEVEL_EXCEPTION_FILTER。)
EXCEPTION_POINTERS結(jié)構(gòu)中包含豐富的異常相關(guān)數(shù)據(jù),主要有異常號、異常發(fā)生時寄存器的值等。
與try-catch一樣,__try-__except也支持調(diào)用棧回溯,也可以嵌套,但沒法重載。
另外,在VC中,還提供__try-__finally塊和__leave關(guān)鍵字,這里不細說了,感興趣的可以查查MSDN。
2. Windows異常處理步驟
回到上文的問題,沒有匹配上catch、__except塊的錯誤或異常將會如何處理呢?原來,包括__except塊在內(nèi),SEH異常處理例程可以有多個,它們的入口地址形成一個鏈?zhǔn)浇Y(jié)構(gòu),這個鏈?zhǔn)浇Y(jié)構(gòu)由Windows操作系統(tǒng)管理。
發(fā)生錯誤或異常后,Windows的處理順序一般如下:
(1)中止當(dāng)前程序的執(zhí)行。
(2)如果程序處于被調(diào)試狀態(tài),向調(diào)試器發(fā)送EXCEPTION_DEBUG_EVENT消息。
(3)如果程序沒有被調(diào)試或者調(diào)試器未能處理異常,查找線程相關(guān)的異常處理例程(如對應(yīng)__except塊)并處理。如果前面查找到的例程返回EXCEPTION_CONTINUE_SEARCH,且線程有多個異常處理例程,則沿這些例程入口地址組成的鏈?zhǔn)浇Y(jié)構(gòu)逐一向后查找,請求下一個例程處理。
(4)如果線程沒有對應(yīng)的異常處理例程,或線程所有例程都返回EXCEPTION_CONTINUE_SEARCH,而且程序處于被調(diào)試狀態(tài),再次通知調(diào)試器。
(5)如果程序沒有被調(diào)試或者調(diào)試器仍未處理異常,則進入主線程的“最終異常處理例程”鏈繼續(xù)查找。
(6)“最終異常處理例程”鏈的最后是Windows默認的系統(tǒng)異常處理程序__CxxUnhandledExceptionFilter(),其處理通常是彈出一個異常對話框,上面顯示一些異常信息,提供“關(guān)閉”、“調(diào)試”等按鈕。
著名的SetUnhandledExceptionFilter()函數(shù)就是在所謂“最終異常處理例程”鏈的__CxxUnhandledExceptionFilter()之前插入一個自定義的異常處理例程,當(dāng)這個例程返回EXCEPTION_EXECUTE_HANDLER時,一般會直接結(jié)束進程。
三、兩種異常處理機制的比較
我能想到的一些特征的比較:
|
C++標(biāo)準(zhǔn)異常
|
SEH
|
局部對象析構(gòu)函數(shù)
|
執(zhí)行
|
局部對象有析構(gòu)函數(shù),且用__try-__exception時,編譯錯誤
|
可重載
|
有參數(shù)類型匹配
|
無條件處理
|
可移植
|
C++都有,不依賴操作系統(tǒng)平臺
|
只有Windows提供
|
程序流程控制
|
catch塊后只能繼續(xù)向下執(zhí)行
|
EXCEPTION_EXECUTE_HANDLER、EXCEPTION_CONTINUE_EXECUTION、
EXCEPTION_CONTINUE_SEARCH三種流程控制,多個處理例程的依次處理
|
數(shù)據(jù)通用
|
各種不同的異常數(shù)據(jù)類型
|
統(tǒng)一結(jié)構(gòu)的異常數(shù)據(jù)
|
四、VC編譯參數(shù)EH
在VC中,你可能會發(fā)現(xiàn)一個怪異的現(xiàn)象,就是try-catch塊無法捕獲像“除0”、“空指針訪問”之類的異常。原來,在VC中一般的錯誤和異常都是用SEH來處理的,不等同于throw拋出的異常。而try-catch對結(jié)構(gòu)化異常的處理,是由編譯參數(shù)EH來控制的。
|
無EH參數(shù)
|
EHs(EHsc)
|
EHa(同Ehac)
|
try-catch
|
不處理異常
|
只處理C++標(biāo)準(zhǔn)異常,代碼優(yōu)化較好
|
處理C++標(biāo)準(zhǔn)異常和結(jié)構(gòu)化異常,代碼優(yōu)化較差
|
__try-__except(VC2005及以后)
|
處理C++標(biāo)準(zhǔn)異常和結(jié)構(gòu)化異常
|
處理C++標(biāo)準(zhǔn)異常和結(jié)構(gòu)化異常
|
處理C++標(biāo)準(zhǔn)異常和結(jié)構(gòu)化異常
|
__try-__except(VC2003及以前)
|
只處理結(jié)構(gòu)化異常
|
只處理結(jié)構(gòu)化異常
|
只處理結(jié)構(gòu)化異常
|
從表中可以看出,EH參數(shù)對__try-__except塊的處理并無影響。
從VC2005開始,SEH也可以統(tǒng)一捕獲和處理C++標(biāo)準(zhǔn)異常。而在VC2003及之前,C++標(biāo)準(zhǔn)異常只能由catch塊來捕獲。
VC2005中,EH參數(shù)默認為EHsc。
附記:關(guān)于自定義SEH異常處例程的編寫,如保存內(nèi)存dump,保存調(diào)用棧,使用調(diào)試相關(guān)的.pdb .map文件等,網(wǎng)上相關(guān)的資料很多,需要可以查詢。
轉(zhuǎn)自:
http://hi.baidu.com/xplot/blog/item/9e7f3154b6e635c2b645ae32.html