1、Windows對於C++異常處理的擴展:
爲了更便捷的在Windows系統中進行異常處理,微軟在C++異常處理的機制上,又增加了擴展,稱爲SEH(Structured Exception Handling),即結構化異常處理;
語法如下:
__try
{
……
}
__except (<exception>)
{
……
}
Windows exception 最常見的問題 :
runtime_error 運行時錯誤:僅在運行時才能檢測到的問題
range_error 運行時錯誤:生產的問題超出了有意義的值域範圍
overflow_error 運行時錯誤:計算上溢
underflow_error 運行時錯誤:計算下溢
logic_error 邏輯錯誤: 可在運行前檢測到的問題
domain_error 邏輯錯誤: 參數的結果值不存在
invalid_argument 邏輯錯誤: 不合適的參數
length_error 邏輯錯誤: 試圖生成一個超出該類型最大長度的對象
out_of_range 邏輯錯誤:使用一個超出有效範圍的值
2、__try -__except:
__try-__except是Microsoft擴展出的C++關鍵字,__try塊中出現錯誤或異常,一般不再用throw拋出,而是直接產生一個EXCEPTION_POINTERS類型的異常數據,然後開始查找SEH例程入口(調試的情況除外)。首先就會找到與__try塊對應的__except塊。__except的參數<exception>與catch的參數作用完全不同,也不類似於函數的參數,它主要是用於控制後面的程序執行,爲這幾個值之一:
EXCEPTION_EXECUTE_HANDLER(1),表示下面執行__except塊內及其後面的代碼
EXCEPTION_CONTINUE_EXECUTION(-1),表示回到拋出異常處繼續向下執行
EXCEPTION_CONTINUE_SEARCH(0), 表示查找下一個異常處理例程入口
Microsoft提供兩個函數GetExceptionCode(), GetExceptionInformation(),分別可以獲取異常號和EXCEPTION_POINTERS類型的異常數據指針。而且這兩個函數只能在__except參數<exception>的表達式中使用。爲了保證這一點,在VC中,編譯器做了特殊處理,如果這兩個函數沒有在正確的位置,將產生編譯錯誤。
3、例:處理除零異常:
#include <windows.h>
Intmain()
{
inti= 1;
intj = 0;
__try
{
i/= j;
}
__except(GetExceptionCode() ==
EXCEPTION_INT_DIVIDE_BY_ZERO ?
EXCEPTION_EXECUTE_HANDLER :
EXCEPTION_CONTINUE_SEARCH)
{
cout<< “除零異常" << endl;
}
}
4、例:處理寫內存異常:
Int main()
{
__try
{
int *p=NULL;
*p = 13;
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
cout << “內存異常" << endl;
}
}
5、Windows異常處理流程:
(1)中止當前程序的執行。
(2)如果程序處於被調試狀態,向調試器發送EXCEPTION_DEBUG_EVENT消息。
(3)如果程序沒有被調試或者調試器未能處理異常,查找線程相關的異常處理例程(如對應__except塊)並處理。如果前面查找到的例程返回 EXCEPTION_CONTINUE_SEARCH,且線程有多個異常處理例程,則沿這些例程入口地址組成的鏈式結構逐一向後查找,請求下一個例程處理。
(4)如果線程沒有對應的異常處理例程,或線程所有例程都返回EXCEPTION_CONTINUE_SEARCH,而且程序處於被調試狀態,再次通知調試器。
(5)如果程序沒有被調試或者調試器仍未處理異常,則進入主線程的“最終異常處理例程”鏈繼續查找。
(6)“最終異常處理例程”鏈的最後是Windows默認的系統異常處理程序__CxxUnhandledExceptionFilter(),其處理通常是彈出一個異常對話框,上面顯示一些異常信息,提 供“關閉”、“調試”等按鈕。
6、 SEH 到 C++異常的轉換
在同一個程序中,如果使用WIN32API它會拋出SHE,使用C++庫函數,它們又會拋出C++異常,Win32API和C++函數混和使用時如果使用兩種異常捕獲機制時,使用起來會影響程序的可讀性,因此C++運行庫提供了_set_se_translator 函數,在SHE異常發生時通過回調方式來轉換SEH異常爲C++異常。在此提供一個轉換的宏來實現轉換。
轉換宏的代碼:
#define INSTALL_SEHCONVERT() ExceptionConvert ecExceptionConvert
class SEHException
{
private :
unsigned int nSE;
public :
SEHException() {}
SEHException( unsigned int n ) : nSE( n ) {}
~SEHException() {}
unsigned int getSeNumber() { return nSE; }
};
class ExceptionConvert
{
public :
ExceptionConvert(){OldFanc = _set_se_translator(trans_func); }
~ExceptionConvert(){_set_se_translator(OldFanc); }
private :
static void trans_func( unsigned int u, EXCEPTION_POINTERS* pExp )
{
throw SEHException(u);
}
_se_translator_function OldFanc;
};
使用上面 INSTALL_SEHCONVERT 宏後就可以使用如下代碼來捕獲SHE異常了
INSTALL_SEHCONVERT();
Try
{
…
}
catch(SEHException &seh){
…
}
7、 同步異常與異步異常
1) VC的C++ Exception 採用兩種模式捕獲異常:同步模式和異步模式。VC的工程的調試版本缺省使用異步模式,工程的發佈版本缺省使用同步模式。在同步模式下,VC的編譯器假定代 碼中只有在顯示使用throw和調用函數的時候纔會引發異常,因此,在同步模式下,VC編譯出的代碼比較小,但在這種模式下,try-catch對不能捕 獲內存訪問異常與算術除零異常等。在異步模式下,VC的編譯器爲try塊內的每一條語句生成異常捕獲代碼,在這種情況下,他能夠捕獲全部的異常,還能保證 棧上對象在解棧中正確釋放。爲了要在發行版本中也能夠捕獲全部異常就需要打開異步模式,但代價是程序編譯出代碼變大,運行速度變慢。
2)編譯選項:
同步模式的編譯選項爲/EHs或者/GX(等同於/EHsc)
異步模式的編譯選項爲/EHa
8、 多線程下的異常捕獲
在創建線程並運行線程的函數中把創建線程的代碼放在try塊中並不會捕獲到線程函數中發生的異常,線程函數中發生的異常只能在線程函數中捕獲。並且每一個線程都需要自己的SHE轉換宏。轉換宏可以放在線程函數的開始部分