24.1 程序的結構
(1)try/except框架
__try{ //被保護的代碼塊 …… } __except(except fileter/*異常過濾程序*/){ //異常處理程序 }
(2)說明
①當__try塊中的代碼發生異常時,__except()中的過濾程序就被調用。
②過濾程序可以是一個簡單的表達式或一個函數(返回值應為EXCEPTION_CONTINUE_SEARCH、EXCEPT_CONTINUE_EXECUTE或EXCEPT_EXECUTE_HANDLER之一)
③過濾表達式中可以調用GetExceptionCode和GetExceptionInformation函數取得正在處理的異常信息。但這兩個函數不能在異常處理程序中使用。
④與try/finally不同,try/except中可以使用return、goto、continue和break,它們並不會導致局部展開。
24.2 異常過濾程序
(1)返回值
標識 |
值 |
說明 |
EXCEPTION_EXECUTE_HANDLER |
1 |
執行except花括號內代碼,同時執行全局展開。最后程序從except花括號后面的第一句代碼繼續運行。 |
EXCEPTION_CONTINUE_SEARCH |
0 |
向外層查找帶except的try塊,並調用對應的異常過濾程序。 |
EXCEPTION_CONTINUE_EXECUTE |
-1 |
重新執行導致異常的那條CPU指令本身。 |
(2)全局展開——異常過濾程序返回EXCEPTION_EXECUTE_HANDLER是會執行全局展開。
①當某個__try塊中的代碼觸發了異常時(也可能是__try塊中調用的函數中引發異常),操作系統會從最靠近引發異常代碼的地方開始從下層往上層查找__except塊(這里的層是指__try塊的嵌套層),對於找到的每一個__except塊,會先計算它的異常過濾器,如果過濾器返回EXCEPTION_CONTINUE_SEARCH,則說明此__except塊不處理此類異常,需要繼續往上層查找,如果某過濾器返回EXCEPTION_EXECUTE_HANDLER則說明此__except塊可以處理此類異常,即找到了異常的處理代碼,此時停止查找,但是在執行該__except塊中的異常處理代碼之前,要先進行全局展開。
②全局展開的過程與查找__except塊的過程類似,只不過這次是查找從底層向上查找__finally塊,查找過程中遇到的每一個__finally塊中的代碼都被執行,直到查找到前面說的處理異常的__except塊那一層停止,這時全局展開完成,然后執行__except塊中的異常處理代碼。
③執行完異常處理代碼之后,指令流從__except塊后的第一條指令開始。從這里也可以看出全局展開也是為了保證__finally語義的正確性,因為指令流從引發異常代碼轉到到__except異常處理代碼時也導致了指令流從__try塊嵌套層中所有與__finally對應的__try塊中流出,由前面的__finally語義說明可知,此時必須要執行全局展開過程以包成__finally語義的正確性。
(3)停止全局展開——將return置於finally塊中可阻止全局展開。【未定義行為,VC2013直接報錯了!】
(4)慎用EXCEPTION_CONTINUE_EXECUTION
①嘗試修復錯誤,出現失敗的實例分析
*pchBuffer = TEXT("J");//C/C++語句 //編譯后的產生的機器指令 MOV EAX,DWORD PTR[pchBuffer] MOV WORD PTR[EAX], 'J' //導致異常的指令。當異常過濾程序捕獲該異常后,修正 //pchBuffer,讓其指向一個正確的地址。並讓系統重新 //執行第二要CPU指令。問題在於寄存器不可能自動更新 //以反映變量pchBuffer的更新,於是該異常又致另一個導 //異常,程序陷入了死循環
②虛擬內存結合SEH可實現按需調撥存儲器,有時能寫出運行速度快和高效的應用程序(見第15章的《如何預訂大塊地址空間和為地址空間稀疏調撥存儲器》
③系統為線程棧建了一個SEH框。當線程試圖訪問棧中尚未調撥存儲器的區域時,會引發一個異常。系統內部的異常過濾程序會捕獲到該異常並在內部調用VirtualAlloc為線程棧調撥更多的存儲,並且返回EXCEPTION_CONTINUE_EXECUTION讓原先拋出異常的指令重新執行下去。
【SEHAndMemory】演示虛擬內存的按需調撥
#include <windows.h> #include <tchar.h> #include <locale.h> #define PAGELIMIT 80 LPBYTE lpNxtPage; DWORD dwPages = 0; DWORD dwPageSize;//頁面大小,一般為4KB INT PageFualtExceptionFilter(DWORD dwCode){ LPVOID lpvResult; //不是非法訪問內存 if (dwCode !=EXCEPTION_ACCESS_VIOLATION){ return EXCEPTION_EXECUTE_HANDLER;//執行except塊的異常處理程序代碼 } //當超過指定的頁面數時 if (dwPages >=PAGELIMIT){ return EXCEPTION_EXECUTE_HANDLER;//執行except塊的異常處理程序代碼 } //非法訪問內存,則為預訂的空間提交下一頁物理存儲器 lpvResult = VirtualAlloc((LPVOID)lpNxtPage, dwPageSize, MEM_COMMIT, PAGE_READWRITE); if (lpvResult == NULL){ return EXCEPTION_EXECUTE_HANDLER;//執行except塊的異常處理程序代碼 } //提交成功 dwPages++; lpNxtPage += dwPageSize; _tprintf(_T("第%d頁提交成功!\n"), dwPages); return EXCEPTION_CONTINUE_EXECUTION; //重新執行觸發異常的那條CPU指令 } int main(){ _tsetlocale(LC_ALL, _T("chs")); LPVOID lpvBase;LPTSTR lpPtr;BOOL bSuccess; SYSTEM_INFO sSysInfo; GetSystemInfo(&sSysInfo); dwPageSize = sSysInfo.dwPageSize; _tprintf(_T("CPU頁面大小為%dKB.\n"), sSysInfo.dwPageSize / 1024); //預訂存儲器 lpvBase = VirtualAlloc(NULL, PAGELIMIT*dwPageSize, MEM_RESERVE, PAGE_NOACCESS); lpPtr = (LPTSTR)(lpNxtPage = (LPBYTE)lpvBase); for (DWORD i = 0; i < PAGELIMIT*dwPageSize/sizeof(TCHAR);i++){ __try{ lpPtr[i] = _T('a');//寫入一個字節的數據 } __except (PageFualtExceptionFilter(GetExceptionCode())){ _tprintf(_T("異常被處理\n")); //ExitProcess(GetLastError()); } } bSuccess = VirtualFree(lpvBase, 0, MEM_RELEASE); _tprintf(_T("釋放操作%s.\n"), bSuccess ? _T("成功") : _T("失敗")); _tsystem(_T("PAUSE")); return 0; }
24.3 GetExceptionCode
(1)GetExceptionCode是個內聯函數,其代碼直接嵌入到被調用的地方(注意與函數調用的區別),它的返回值表明剛剛發生的異常的類型(定義在WinBase.h中,如EXCEPTION_ACCESS_VIOLATION)
(2)該函數只能在異常過濾程序里(即__except之后的小括號內)或者異常處理程序的代碼里調用(__except塊后面的花括號內),但不能在異常過濾函數中使用。
//合法代碼 __try{ y = 0; x = / y; } __except ((GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH){ //在__except塊的小括內使用,合法 switch (GetExceptionCode()){ //__except塊的花括中使用,合法! ...... } } |
//非法代碼 LONG MyFilter(void){} { //在異常過濾函數中使用GetExceptionCode,不合法! return ((GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH); }
__try{ y = 0; x = 4 / y; } __except(MyFilter()){ //可改成將GetExceptionCode作為參數傳給MyFilter的形式。 //處理異常 } |
(3)異常錯誤代碼的規則
位 |
31-30 |
29 |
28 |
27-16 |
15-0 |
內容 |
嚴重性 |
Microsoft/ Customer |
保留位 |
設備代碼 |
異常代碼 |
含義 |
0=Success 1=Informational 2=Warning 3=error |
0=Mircosoft所定義的代碼 1=Customer所定義的代碼 |
一直為0 |
前256個值為Micorsoft所保留。(如FACILITY_NULL(0)表示該異常可以在系統任何設備出現,並不只發生在一些特定的設備上) |
由Microsoft/ Customer所定義的代碼 |
24.4 GetExceptionInformation
(1)GetExceptionInformation可獲取異常發生時,系統向發生異常的線程棧中壓入的EXCEPTION_RECORD、CONTEXT和EXCEPTION_POINTERS結構中的異常信息或CPU有關的信息
(2)這個函數只能在異常過濾程序中調用(即__except塊的小括號),因為EXCEPT_RECORD、CONTEXT和EXCEPTION_POINTER數據結構只有在系統計算異常過濾程序時才有效。一旦控制流被轉移到其他地方,這些棧上的數據結構會被銷毀。但我們可以自己保存他們,以備后用。
//保存棧中異常信息的方法 void FuncSkunk(){ //聲明一些可以保存異常信息的結構體,須在try塊外面聲明 EXCEPTION_RECORD SavedExceptRec; CONTEXT SavedContext; __try{ } __except ( //注意逗號表達式,取最后一個表達式為整個表達式的值。 SavedExceptRec = *(GetExceptionInformation())->ExceptionRecord, SavedContext = *(GetExceptionInformation())->ContextRecord, EXCEPTION_EXECUTE_HANDLER){ //異常處理 } }
(3)EXCEPTION_RECORD結構體——剛發生的異常的詳細信息
字段 |
說明 |
DWORD ExceptionCode |
異常代碼,就是GetExceptionCode函數的返回值 |
DWORD ExceptionFlags |
異常標志 0—表示繼續的異常;EXCEPTION_NONCONTINUABLE—不可繼續的異常,如果程序試圖在一個不可繼續的異常之后繼續執行,會引發EXCEPTION_NONCONTINUABLE_EXCEPTION異常。 |
PEXCEPTION_RECORD pExceptionRecord |
指向另一個未處理異常的EXCEPTION_RECORD結構。(即嵌套異常發生時,異常會形成異常鏈) |
PVOID ExceptionAddress |
導致異常的CPU指令的地址 |
DWORD NumberParameters |
ExceptionInformation數組里元素的個數。對絕大部分的異常來說,這個值為0。 |
ULONG_PTR ExceptionInformation [EXCEPTION_MAXIMUM_PARAMETERS] |
描述異常的附加參數數組,對絕大部分的異常來說,這個數組元素都未定義。 |
24.5 軟件異常——RaiseException函數
參數 |
說明 |
DWORD dwExceptionCode |
要拋出異常的標識符,可參考《異常錯誤代碼規則》來編寫 |
DWORD dwExceptionFlags |
必須下列兩者之一 0: EXCEPTION_NONCONTINUABLE:異常不可繼續,即不能再異常過濾程序中返回EXCEPTION_CONTINUE_EXECUTE,否則重新執行那條導致錯誤的CPU指令會繼續拋出一個新的EXCEPTION_NONCONTINUABLE_EXCEPTION異常。 |
DWORD nNumberOfArguments |
用來傳遞有關拋出異常的附加信息。一般不需要。可將nNumberOfArgument設為0。pArguments設為NULL。 |
Const ULONG_PTR* pArguments |
|
返回值 |
void |
【RaiseException程序】——演示自己拋出的軟件異常
#include <tchar.h> #include <windows.h> DWORD FilterFunction(){ _tprintf(_T("1")); //第1句被輸出的語句 return EXCEPTION_EXECUTE_HANDLER; } int main(){ __try{ __try{ RaiseException(1, 0, 0, NULL); } __finally{ _tprintf(_T("2")); //第2句被輸出的語句 } } __except (FilterFunction()){ _tprintf(_T("3\n")); //第3句被輸出的語句 } _tsystem(_T("PAUSE"));
return 0; }