__try,__except,__finally,__leave异常模型机
導(dǎo)讀:?
從本篇文章開始,將全面闡述__try,__except,__finally,__leave異常模型機(jī)制,它也即是Windows系列操作系統(tǒng)平臺上提供的SEH模型。主人公阿愚將在這里與大家分享SEH( 結(jié)構(gòu)化異常處理)的學(xué)習(xí)過程和經(jīng)驗(yàn)總結(jié)。 深入理解請參閱<<windows 核心編程>>第23, 24章.
SEH實(shí)際包含兩個主要功能:結(jié)束處理(termination handling)和異常處理(exception handling)?
每當(dāng)你建立一個try塊,它必須跟隨一個finally塊或一個except塊。
一個try 塊之后不能既有finally塊又有except塊。但可以在try - except塊中嵌套try - finally塊,反過來
也可以。
__try? __finally關(guān)鍵字用來標(biāo)出結(jié)束處理程序兩段代碼的輪廓
不管保護(hù)體(try塊)
是如何退出的。不論你在保護(hù)體中使用return,還是goto,或者是longjump,結(jié)束處理程序
(finally塊)都將被調(diào)用。
在try使用__leave關(guān)鍵字會引起跳轉(zhuǎn)到try塊的結(jié)尾
SEH有兩項非常強(qiáng)大的功能。當(dāng)然,首先是異常處理模型了,因此,這篇文章首先深入闡述SEH提供的異常處理模型。另外,SEH還有一個特別強(qiáng)大的功能,這將在下一篇文章中進(jìn)行詳細(xì)介紹。
try-except入門
SEH的異常處理模型主要由try-except語句來完成,它與標(biāo)準(zhǔn)C++所定義的異常處理模型非常類似,也都是可以定義出受監(jiān)控的代碼模塊,以及定義異常處理模塊等。還是老辦法,看一個例子先,代碼如下:?
//seh-test.c
呵呵!是不是很簡單,而且與C++異常處理模型很相似。當(dāng)然,為了與C++異常處理模型相區(qū)別,VC編譯器對關(guān)鍵字做了少許變動。首先是在每個關(guān)鍵字加上兩個下劃線作為前綴,這樣既保持了語義上的一致性,另外也盡最大可能來避免了關(guān)鍵字的有可能造成名字沖突而引起的麻煩等;其次,C++異常處理模型是使用catch關(guān)鍵字來定義異常處理模塊,而SEH是采用__except關(guān)鍵字來定義。并且,catch關(guān)鍵字后面往往好像接受一個函數(shù)參數(shù)一樣,可以是各種類型的異常數(shù)據(jù)對象;但是__except關(guān)鍵字則不同,它后面跟的卻是一個表達(dá)式(可以是各種類型的表達(dá)式,后面會進(jìn)一步分析)。
try-except進(jìn)階
與C++異常處理模型很相似,在一個函數(shù)中,可以有多個try-except語句。它們可以是一個平面的線性結(jié)構(gòu),也可以是分層的嵌套結(jié)構(gòu)。例程代碼如下:
// 例程1
// 平面的線性結(jié)構(gòu)
// 例程2
// 分層的嵌套結(jié)構(gòu)
// 例程3
// 分層的嵌套在__except模塊中
?1. 受監(jiān)控的代碼模塊被執(zhí)行(也即__try定義的模塊代碼);
2. 如果上面的代碼執(zhí)行過程中,沒有出現(xiàn)異常的話,那么控制流將轉(zhuǎn)入到__except子句之后的代碼模塊中;
3. 否則,如果出現(xiàn)異常的話,那么控制流將進(jìn)入到__except后面的表達(dá)式中,也即首先計算這個表達(dá)式的值,之后再根據(jù)這個值,來決定做出相應(yīng)的處理。這個值有三種情況,如下:
EXCEPTION_CONTINUE_EXECUTION?(–1) 異常被忽略,控制流將在異常出現(xiàn)的點(diǎn)之后,繼續(xù)恢復(fù)運(yùn)行。
EXCEPTION_CONTINUE_SEARCH (0) 異常不被識別,也即當(dāng)前的這個__except模塊不是這個異常錯誤所對應(yīng)的正確的異常處理模塊。系統(tǒng)將繼續(xù)到上一層的try-except域中繼續(xù)查找一個恰當(dāng)?shù)腳_except模塊。
EXCEPTION_EXECUTE_HANDLER (1) 異常已經(jīng)被識別,也即當(dāng)前的這個異常錯誤,系統(tǒng)已經(jīng)找到了并能夠確認(rèn),這個__except模塊就是正確的異常處理模塊。控制流將進(jìn)入到__except模塊中。
?
try-except深入
上面的內(nèi)容中已經(jīng)對try-except進(jìn)行了全面的了解,但是有一點(diǎn)還沒有闡述到。那就是如何在__except模塊中獲得異常錯誤的相關(guān)信息,這非常關(guān)鍵,它實(shí)際上是進(jìn)行異常錯誤處理的前提,也是對異常進(jìn)行分層分級別處理的前提。可想而知,如果沒有這些起碼的信息,異常處理如何進(jìn)行?因此獲取異常信息非常的關(guān)鍵。Windows提供了兩個API函數(shù),如下:
?
LPEXCEPTION_POINTERS?GetExceptionInformation(VOID);
DWORD?GetExceptionCode(VOID);
其中GetExceptionCode()返回錯誤代碼,而GetExceptionInformation()返回更全面的信息,看它函數(shù)的聲明,返回了一個LPEXCEPTION_POINTERS類型的指針變量。那么EXCEPTION_POINTERS結(jié)構(gòu)如何呢?如下,
?
typedef struct _EXCEPTION_POINTERS { // exp?
PEXCEPTION_RECORD ExceptionRecord;?
PCONTEXT ContextRecord;?
} EXCEPTION_POINTERS;
?
呵呵!仔細(xì)瞅瞅,這是不是和上一篇文章中,用戶程序所注冊的異常處理的回調(diào)函數(shù)的兩個參數(shù)類型一樣。是的,的確沒錯!其中EXCEPTION_RECORD類型,它記錄了一些與異常相關(guān)的信息;而CONTEXT數(shù)據(jù)結(jié)構(gòu)體中記錄了異常發(fā)生時,線程當(dāng)時的上下文環(huán)境,主要包括寄存器的值。因此有了這些信息,__except模塊便可以對異常錯誤進(jìn)行很好的分類和恢復(fù)處理。不過特別需要注意的是,這兩個函數(shù)只能是在__except后面的括號中的表達(dá)式作用域內(nèi)有效,否則結(jié)果可能沒有保證(至于為什么,在后面深入分析異常模型的實(shí)現(xiàn)時候,再做詳細(xì)闡述)。看一個例程吧!代碼如下:
int exception_access_violation_filter(LPEXCEPTION_POINTERS p_exinfo) {if(p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION){printf("存儲保護(hù)異常\n");return 1;}else return 0; }int exception_int_divide_by_zero_filter(LPEXCEPTION_POINTERS p_exinfo) {if(p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO){printf("被0除異常\n");return 1;}else return 0; }void main() {__try{__try{int* p;// 下面將導(dǎo)致一個異常p = 0;*p = 45;}// 注意,__except模塊捕獲一個存儲保護(hù)異常 __except(exception_access_violation_filter(GetExceptionInformation())){puts("內(nèi)層的except塊中");}//可以在此寫除0異常的語句int b = 0;int a = 1 / b;}// 注意,__except模塊捕獲一個被0除異常 __except(exception_int_divide_by_zero_filter(GetExceptionInformation())) {puts("外層的except塊中");} }上面的程序運(yùn)行結(jié)果如下:
存儲保護(hù)異常
內(nèi)層的except塊中
Press any key to continue
?
呵呵!感覺不錯,大家可以在上面的程序基礎(chǔ)之上改動一下,讓它拋出一個被0除異常,看程序的運(yùn)行結(jié)果是不是如預(yù)期那樣。
最后還有一點(diǎn)需要闡述,在C++的異常處理模型中,有一個throw關(guān)鍵字,也即在受監(jiān)控的代碼中拋出一個異常,那么在SEH異常處理模型中,是不是也應(yīng)該有這樣一個類似的關(guān)鍵字或函數(shù)呢?是的,沒錯!SEH異常處理模型中,對異常劃分為兩大類,第一種就是上面一些例程中所見到的,這類異常是系統(tǒng)異常,也被稱為硬件異常;還有一類,就是程序中自己拋出異常,被稱為軟件異常。怎么拋出呢?還是Windows提供了的API函數(shù),它的聲明如下:
?
VOID?RaiseException(
DWORD dwExceptionCode, // exception code
DWORD dwExceptionFlags, // continuable exception flag
DWORD nNumberOfArguments, // number of arguments in array
CONST DWORD *lpArguments // address of array of arguments
);
?
很簡單吧!實(shí)際上,在C++的異常處理模型中的throw關(guān)鍵字,最終也是對RaiseException()函數(shù)的調(diào)用,也即是說,throw是RaiseException的上層封裝的更高級一類的函數(shù),這以后再詳細(xì)分析它的代碼實(shí)現(xiàn)。這里還是看一個簡單例子吧!代碼如下:
int seh_filer(int code) {switch(code){case EXCEPTION_ACCESS_VIOLATION :printf("存儲保護(hù)異常,錯誤代碼:%x\n", code);break;case EXCEPTION_DATATYPE_MISALIGNMENT :printf("數(shù)據(jù)類型未對齊異常,錯誤代碼:%x\n", code);break;case EXCEPTION_BREAKPOINT :printf("中斷異常,錯誤代碼:%x\n", code);break;case EXCEPTION_SINGLE_STEP :printf("單步中斷異常,錯誤代碼:%x\n", code);break;case EXCEPTION_ARRAY_BOUNDS_EXCEEDED :printf("數(shù)組越界異常,錯誤代碼:%x\n", code);break;case EXCEPTION_FLT_DENORMAL_OPERAND :case EXCEPTION_FLT_DIVIDE_BY_ZERO :case EXCEPTION_FLT_INEXACT_RESULT :case EXCEPTION_FLT_INVALID_OPERATION :case EXCEPTION_FLT_OVERFLOW :case EXCEPTION_FLT_STACK_CHECK :case EXCEPTION_FLT_UNDERFLOW :printf("浮點(diǎn)數(shù)計算異常,錯誤代碼:%x\n", code);break;case EXCEPTION_INT_DIVIDE_BY_ZERO :printf("被0除異常,錯誤代碼:%x\n", code);break;case EXCEPTION_INT_OVERFLOW :printf("數(shù)據(jù)溢出異常,錯誤代碼:%x\n", code);break;case EXCEPTION_IN_PAGE_ERROR :printf("頁錯誤異常,錯誤代碼:%x\n", code);break;case EXCEPTION_ILLEGAL_INSTRUCTION :printf("非法指令異常,錯誤代碼:%x\n", code);break;case EXCEPTION_STACK_OVERFLOW :printf("堆棧溢出異常,錯誤代碼:%x\n", code);break;case EXCEPTION_INVALID_HANDLE :printf("無效句病異常,錯誤代碼:%x\n", code);break;default :if(code & (1<<29))printf("用戶自定義的軟件異常,錯誤代碼:%x\n", code);elseprintf("其它異常,錯誤代碼:%x\n", code);break;}return 1; }void main() {__try{puts("try塊中");// 注意,主動拋出一個軟異常RaiseException(0xE0000001, 0, 0, 0);}__except(seh_filer(GetExceptionCode())){puts("except塊中");}}上面的程序運(yùn)行結(jié)果如下:
hello
try塊中
用戶自定義的軟件異常,錯誤代碼:e0000001
except塊中
world
Press any key to continue
?
上面的程序很簡單,這里不做進(jìn)一步的分析。我們需要重點(diǎn)討論的是,在__except模塊中如何識別不同的異常,以便對異常進(jìn)行很好的分類處理。毫無疑問,它當(dāng)然是通過GetExceptionCode()或GetExceptionInformation ()函數(shù)來獲取當(dāng)前的異常錯誤代碼,實(shí)際也即是DwExceptionCode字段。異常錯誤代碼在winError.h文件中定義,它遵循Windows系統(tǒng)下統(tǒng)一的錯誤代碼的規(guī)則。每個DWORD被劃分幾個字段,如下表所示:
例如我們可以在winbase.h文件中找到EXCEPTION_ACCESS_VIOLATION的值為0 xC0000005,將這個異常代碼值拆開,來分析看看它的各個bit位字段的涵義。
C 0 0 0 0 0 0 5 (十六進(jìn)制)
1100 0000 0000 0000 0000 0000 0000 0101 (二進(jìn)制)
第3 0位和第3 1位都是1,表示該異常是一個嚴(yán)重的錯誤,線程可能不能夠繼續(xù)往下運(yùn)行,必須要及時處理恢復(fù)這個異常。第2 9位是0,表示系統(tǒng)中已經(jīng)定義了異常代碼。第2 8位是0,留待后用。第1 6 位至2 7位是0,表示是FACILITY_NULL設(shè)備類型,它代表存取異常可發(fā)生在系統(tǒng)中任何地方,不是使用特定設(shè)備才發(fā)生的異常。第0位到第1 5位的值為5,表示異常錯誤的代碼。
如果程序員在程序代碼中,計劃拋出一些自定義類型的異常,必須要規(guī)劃設(shè)計好自己的異常類型的劃分,按照上面的規(guī)則來填充異常代碼的各個字段值,如上面示例程序中拋出一個異常代碼為0xE0000001軟件異常。
總結(jié)
(1) C++異常模型用try-catch語法定義,而SEH異常模型則用try-except語法;
(2) 與C++異常模型相似,try-except也支持多層的try-except嵌套。
(3) 與C++異常模型不同的是,try-except模型中,一個try塊只能是有一個except塊;而C++異常模型中,一個try塊可以有多個catch塊。
(4) 與C++異常模型相似,try-except模型中,查找搜索異常模塊的規(guī)則也是逐級向上進(jìn)行的。但是稍有區(qū)別的是,C++異常模型是按照異常對象的類型來進(jìn)行匹配查找的;而try-except模型則不同,它通過一個表達(dá)式的值來進(jìn)行判斷。如果表達(dá)式的值為1(EXCEPTION_EXECUTE_HANDLER),表示找到了異常處理模塊;如果值為0(EXCEPTION_CONTINUE_SEARCH),表示繼續(xù)向上一層的try-except域中繼續(xù)查找其它可能匹配的異常處理模塊;如果值為-1(EXCEPTION_CONTINUE_EXECUTION),表示忽略這個異常,注意這個值一般很少用,因?yàn)樗苋菀讓?dǎo)致程序難以預(yù)測的結(jié)果,例如,死循環(huán),甚至導(dǎo)致程序的崩潰等。
(5) __except關(guān)鍵字后面跟的表達(dá)式,它可以是各種類型的表達(dá)式,例如,它可以是一個函數(shù)調(diào)用,或是一個條件表達(dá)式,或是一個逗號表達(dá)式,或干脆就是一個整型常量等等。最常用的是一個函數(shù)表達(dá)式,并且通過利用GetExceptionCode()或GetExceptionInformation ()函數(shù)來獲取當(dāng)前的異常錯誤信息,便于程序員有效控制異常錯誤的分類處理。
(6) SEH異常處理模型中,異常被劃分為兩大類:系統(tǒng)異常和軟件異常。其中軟件異常通過RaiseException()函數(shù)拋出。RaiseException()函數(shù)的作用類似于C++異常模型中的throw語句。
第3種情況,也即由于出現(xiàn)異常而導(dǎo)致的“全局展開”,對于程序員而言,這也許是無法避免的,因?yàn)槟阍诶卯惓L幚頇C(jī)制提高程序可靠健壯性的同時,不可避免的會引起性能上其它的一些開銷。呵呵!這世界其實(shí)也算瞞公平的,有得必有失。
但是,對于第2種情況,程序員完全可以有效地避免它,避免“局部展開”引起的不必要的額外開銷。實(shí)際這也是與結(jié)構(gòu)化程序設(shè)計思想相一致的,也即一個程序模塊應(yīng)該只有一個入口和一個出口,程序模塊內(nèi)盡量避免使用goto語句等。但是,話雖如此,有時為了提高程序的可讀性,程序員在編寫代碼時,有時可能不得不采用一些與結(jié)構(gòu)化程序設(shè)計思想相悖的做法,例如,在一個函數(shù)中,可能有多處的return語句。針對這種情況,SEH提供了一種非常有效的折衷方案,那就是__leave關(guān)鍵字所起的作用,它既具有像goto語句和return語句那樣類似的作用(由于檢測到某個程序運(yùn)行中的錯誤,需要馬上離開當(dāng)前的 __try塊作用域),但是又避免了“局部展開” 的額外開銷。還是看個例子吧!代碼如下:? #include <stdio.h>void test() { puts("hello"); __try { int* p; puts("__try塊中");// 直接跳出當(dāng)前的__try作用域 __leave; p = 0; *p = 25; } __finally { // 這里會被執(zhí)行嗎?當(dāng)然 puts("__finally塊中"); }puts("world"); }void main() { __try { test(); } __except(1) { puts("__except塊中"); } }
上面的程序運(yùn)行結(jié)果如下:
hello
__try塊中
__finally塊中
world
Press any key to continue
總結(jié)
以上是生活随笔為你收集整理的__try,__except,__finally,__leave异常模型机的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: STL 算法
- 下一篇: 两个大文件找出相同的一条记录