jimmy 戰(zhàn)志杰 編譯
本文編譯自Jeffrey Richter先生的“Advanced Windows”部分章節(jié)。 1、引言 在“C++中例外的處理”一文中(見計(jì)算機(jī)世界網(wǎng)2001年12月20日),我們討論了C++中的例外(或異常)處理。本文將進(jìn)一步探討Visual C++中的結(jié)構(gòu)異常處理。 想象一下,如果在編程過(guò)程中你不需要考慮任何錯(cuò)誤,你的程序永遠(yuǎn)不會(huì)出錯(cuò),有足夠的內(nèi)存,你需要的文件永遠(yuǎn)存在,這將是一件多么愉快的事。這時(shí)你的程序不需要太多的if語(yǔ)句轉(zhuǎn)來(lái)轉(zhuǎn)去,非常容易寫,容易讀,也容易理解。如果你認(rèn)為這樣的編程環(huán)境是一種夢(mèng)想,那么你就會(huì)喜歡結(jié)構(gòu)異常處理(structu reed exception handling)。 結(jié)構(gòu)異常處理的本質(zhì)就是讓你專心于如何去完成你的任務(wù)。如果在程序運(yùn)行過(guò)程中出現(xiàn)任何錯(cuò)誤,系統(tǒng)會(huì)接收(catch)并通知(notify)你。雖然利用結(jié)構(gòu)異常處理你不可能完全忽略你的程序出錯(cuò)的可能性,但是結(jié)構(gòu)異常處理確確實(shí)實(shí)允許你將你的主要任務(wù)與錯(cuò)誤處理分離開來(lái)。這種分離使得你可以集中精力于你的工作,而在以后在考慮可能的錯(cuò)誤。 結(jié)構(gòu)異常處理的主要工作是由編譯器來(lái)完成的,而不是由操作系統(tǒng)。編譯器在遇到例外程序段時(shí)需要產(chǎn)生額外的特殊代碼來(lái)支持結(jié)構(gòu)異常處理。所以,每一個(gè)編譯器產(chǎn)品供應(yīng)商可能使用自己的語(yǔ)法和規(guī)定。這里我們采用微軟的Visual C++編譯器來(lái)進(jìn)行討論。 注意不要將這里討論的結(jié)構(gòu)異常處理與C++中的異常處理混為一談。C++中的異常處理是另一種形式的異常處理,它使用了C++的關(guān)鍵詞catch和throw。 微軟最早在Visual C++版本2.0引進(jìn)結(jié)構(gòu)異常處理。結(jié)構(gòu)異常處理主要由兩部分組成:中斷處理(termination handling)和例外處理(exception handling)。 2、中斷處理句柄(termination handler) 2.1、中斷處理句柄定義 中斷處理句柄保證了,不論進(jìn)程如何離開另一程序段--這里稱之為守衛(wèi)體(guarded body),該句柄內(nèi)的程序段永遠(yuǎn)會(huì)被調(diào)用和執(zhí)行。微軟的Visual C++編譯器的中斷處理句柄語(yǔ)法為 __try { // Guarded body . . . } __finally { // Termination handler . . . } 這里的__try和__finally勾畫出了中斷處理句柄的兩個(gè)部分。在上面的例子中,操作系統(tǒng)和編譯器一起保證了不論包含在__try內(nèi)的程序段出現(xiàn)何種情況,包含在__finally內(nèi)的程序段永遠(yuǎn)會(huì)被運(yùn)行。不論你在__try內(nèi)的程序段中調(diào)用return、goto或longjump,__finally內(nèi)的中斷處理句柄永遠(yuǎn)會(huì)被調(diào)用。其流程為 // 1、執(zhí)行try程序段前的代碼 __try { // 2、執(zhí)行try程序段內(nèi)的代碼 } __finally { // 3、執(zhí)行finally程序段內(nèi)的代碼 } // 4、執(zhí)行finally程序段后的代碼 2.2、幾個(gè)例子 下面我們通過(guò)幾個(gè)具體例子來(lái)討論中斷處理句柄是如何工作的。 2.2.1、例1--Funcenstein1 清單一給出了我們的第一個(gè)例子。 DWORD Funcenstein1(void) { DWORD dwTemp; // 1. Do any processing here. . . . __try { // 2. request permission to access protected data, and then use it. WaitForSingleObject(g_hSem, INFINITE); g_dwProtectedData = 5; dwTemp = g_dwProtectedData; } __finally { // 3. Allow others to use protected data. ReleaseSemaphore(g_hSem, 1, NULL); } // 4. Continue processing. return (dwTemp); } 例1 Funcenstein1函數(shù)代碼 在函數(shù)Funcenstein1中,我們使用了try-finally程序塊。但是它們并沒(méi)有為我們做多少工作:等待一個(gè)指示燈信號(hào),改變保護(hù)數(shù)據(jù)的內(nèi)容,將新的數(shù)據(jù)指定給一個(gè)局域變量dwTemp,釋放指示燈信號(hào),返回新的數(shù)據(jù)給調(diào)用函數(shù)。 2.2.2、例2--Funcenstein2 現(xiàn)在讓我們對(duì)Funcenstein1稍稍做一些改動(dòng),看看會(huì)出現(xiàn)什么情況(見清單二)。 DWORD Funcenstein2(void) { DWORD dwTemp; // 1. Do any processing here. . . . __try { // 2. request permission to access protected data, and then use it. WaitForSingleObject(g_hSem, INFINITE); g_dwProtectedData = 5; dwTemp = g_dwProtectedData; // Return the new value. return (dwTemp); } __finally { // 3. Allow others to use protected data. ReleaseSemaphore(g_hSem, 1, NULL); } // 4. Continue processing--this code will never execute in this version. dwTemp = 9; return (dwTemp); } 例2 Funcenstein2函數(shù)代碼 在函數(shù)Funcenstein2中,我們?cè)趖ry程序段里加入了一個(gè)return返回語(yǔ)句。該返回語(yǔ)句告訴編譯器,你想離開函數(shù)Funcenstein2并返回dwTemp內(nèi)的內(nèi)容5給調(diào)用函數(shù)。然而,如果此返回語(yǔ)句被執(zhí)行,本線程永遠(yuǎn)不會(huì)釋放指示燈信號(hào),其它線程也就永遠(yuǎn)不會(huì)得到該指示燈信號(hào)。你可以想象,在多線程程序中這是一個(gè)多么嚴(yán)重的問(wèn)題。 但是,使用了中斷處理句柄避免了這種情況發(fā)生。當(dāng)返回語(yǔ)句試圖離開try程序段時(shí),編譯器保證了在finally程序段內(nèi)的代碼得到執(zhí)行。所以,finally程序段內(nèi)的代碼保證會(huì)在try程序段中的返回語(yǔ)句前執(zhí)行。在函數(shù)Funcenstein2中,將調(diào)用ReleaseSemaphore放在finally程序段內(nèi)保證了指示燈信號(hào)會(huì)得到釋放。 在finally程序段內(nèi)的代碼被執(zhí)行后,函數(shù)Funcenstein2立即返回。這樣,因?yàn)閠ry程序段內(nèi)的return返回語(yǔ)句,任何finally程序段后的代碼都不會(huì)被執(zhí)行。因而Funcenstein2返回值是5,而不是9。 必須指出的是,當(dāng)遇到例2中這種過(guò)早返回語(yǔ)句時(shí),編譯器需要產(chǎn)生額外的代碼以保證finally程序段內(nèi)的代碼的執(zhí)行。此過(guò)程稱作為局域展開。當(dāng)然,這必然會(huì)降低整個(gè)程序的效率。所以,你應(yīng)該盡量避免使用這類代碼。在后面我們會(huì)討論關(guān)鍵詞__leave,它可以幫助我們避免編寫出現(xiàn)局域展開一類的代碼。 2.2.3、例3--Funcenstein3 現(xiàn)在讓我們對(duì)Funcenstein2做進(jìn)一步改動(dòng),看看會(huì)出現(xiàn)什么情況(見例3)。 DWORD Funcenstein3(void) { DWORD dwTemp; // 1. Do any processing here. . . . __try { // 2. request permission to access protected data, and then use it. WaitForSingleObject(g_hSem, INFINITE); g_dwProtectedData = 5; dwTemp = g_dwProtectedData; // Try to jump over the finally block. goto ReturnValue; } __finally { // 3. Allow others to use protected data. ReleaseSemaphore(g_hSem, 1, NULL); } dwTemp = 9; // 4. Continue processing. ReturnValue: return (dwTemp); } 例3 Funcenstein3函數(shù)代碼 在函數(shù)Funcenstein3中,當(dāng)遇到goto語(yǔ)句時(shí)編譯器會(huì)產(chǎn)生額外的代碼以保證finally程序段內(nèi)的代碼得到執(zhí)行。但是,這一次finally程序段后ReturnValue標(biāo)簽后面的代碼會(huì)被執(zhí)行,因?yàn)閠ry或finally程序段內(nèi)沒(méi)有返回語(yǔ)句。函數(shù)的返回值是5。同樣,由于goto語(yǔ)句打斷了從try程序段到finally程序段的自然流程,程序的效率會(huì)降低。 2.2.4、例4--Funcfurter1 現(xiàn)在讓我們來(lái)看中斷處理真正展現(xiàn)其功能的一個(gè)例子。(見例4)。 DWORD Funcfurter1(void) { DWORD dwTemp; // 1. Do any processing here. . . . __try { // 2. request permission to access protected data, and then use it. WaitForSingleObject(g_hSem, INFINITE); dwTemp = Funcinator(g_dwProtectedData); } __finally { // 3. Allow others to use protected data. ReleaseSemaphore(g_hSem, 1, NULL); } // 4. Continue processing. return (dwTemp); } 例4 Funcfurter1函數(shù)代碼 設(shè)想try程序段內(nèi)調(diào)用的Funcinator函數(shù)具有某種缺陷而造成無(wú)效內(nèi)存讀寫。在16位視窗應(yīng)用程序中,這會(huì)導(dǎo)致一個(gè)已定義好的錯(cuò)誤信息對(duì)話框出現(xiàn)。在用戶關(guān)閉對(duì)話框的同時(shí)該應(yīng)用程序也終止運(yùn)行。在不具有try-finally的Win32應(yīng)用程序中,這會(huì)導(dǎo)致程序終止運(yùn)行,指示燈信號(hào)永遠(yuǎn)不會(huì)得到釋放。這就造成了等待該指示燈信號(hào)的其它線程會(huì)永遠(yuǎn)等待下去。而將ReleaseSemaphore放在finally程序段內(nèi)則從根本上保證了不論何種情況出現(xiàn)指示燈信號(hào)都會(huì)得到釋放。 如果中斷處理句柄能夠處理由于無(wú)效內(nèi)存讀寫而造成的程序中斷,我們就完全有理由相信它能夠處理諸如setjump/longjump、break和continue這類的中斷轉(zhuǎn)移。事實(shí)也正是這樣。 2.3、小測(cè)試 下面一個(gè)例子(見清單五)請(qǐng)讀者猜測(cè)一下函數(shù)FuncaDoodleDoo的返回值。(答案為14) DWORD FuncaDoodleDoo(void) { DWORD dwTemp = 0; while (dwTemp 〈 10) { __try { if (dwTemp == 2) continue; if (dwTemp == 3) break; } __finally { dwTemp++; } dwTemp++; } dwTemp += 10; return (dwTemp); } FuncaDoodleDoo函數(shù)代碼 雖然中斷處理句柄能夠接收出現(xiàn)在try程序段內(nèi)的絕大部分異常情況,但是如果線程或進(jìn)程中斷執(zhí)行的話,則finally程序段內(nèi)的代碼不會(huì)被執(zhí)行。調(diào)用ExitThread或ExitProcess就會(huì)立即造成線程或進(jìn)程的中斷,而不會(huì)執(zhí)行finally程序段。另外,如果其它的應(yīng)用程序調(diào)用ExitThread或ExitProcess而造成你的線程或進(jìn)程中斷,你程序中的finally程序段也不會(huì)被執(zhí)行。一些C函數(shù)如abort會(huì)調(diào)用ExitProcess,也會(huì)導(dǎo)致你的finally程序段不被執(zhí)行。對(duì)此你無(wú)能為力。但你可以防止你自己提早調(diào)用ExitThread或ExitProcess。 2.4、應(yīng)用例子 我們已經(jīng)討論了中斷處理句柄的句法及語(yǔ)法,F(xiàn)在我們進(jìn)一步討論如何利用中斷處理句柄來(lái)簡(jiǎn)化一個(gè)比較復(fù)雜的編程問(wèn)題。 首先讓我們來(lái)看一個(gè)沒(méi)有使用中斷處理句柄的例子,程序源代碼見例6。 BOOL Funcarama1 (void) { HANDLE hFile = INVALID_HANDLE_VALUE; LPVOID lpBuf = NULL; DWORD dwNumBytesRead; BOOL fOk; hFile = CreateFile("SOMEDATA.DAT", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) { return (FALSE); } lpBuf = VitualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE); if (lpBuf == NULL) { CloseHandle(hFile); return (FALSE); } fOk = ReadFile(hFile, lpBuf, 1024, &dwNumBytesRead, NULL); if (!fOk || (dwNumBytesRead == 0)) { VirtualFree(lpBuf, MEM_RELEASE | MEM_DECOMMIT); CloseHandle(hFile); return (FALSE); } // Do some calculation on the data. . . . // Clean up all the resources. VirtualFree(lpBuf, MEM_RELEASE | MEM_DECOMMIT); CloseHandle(hFile); return (TRUE); } 例6 沒(méi)有使用中斷處理句柄的Funcarama1函數(shù)代碼 在上例Funcarama1函數(shù)中,所有的錯(cuò)誤診斷使得該函數(shù)難以理解、維護(hù)和修改。當(dāng)然,我們可以對(duì)Funcarama1函數(shù)進(jìn)行一些改動(dòng),使其易于理解(見例7)。 BOOL Funcarama2 (void) { HANDLE hFile = INVALID_HANDLE_VALUE; LPVOID lpBuf = NULL; DWORD dwNumBytesRead; BOOL fOk, fSuccess = FALSE; hFile = CreateFile("SOMEDATA.DAT", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hFile != INVALID_HANDLE_VALUE) { lpBuf = VitualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE); if (lpBuf != NULL) { fOk = ReadFile(hFile, lpBuf, 1024, &dwNumBytesRead, NULL); if (fOk || (dwNumBytesRead != 0)) { // Do some calculation on the data. . . . fSuccess = TRUE; } } VirtualFree(lpBuf, MEM_RELEASE | MEM_DECOMMIT); } CloseHandle(hFile); return (fSuccess); } 例7 沒(méi)有使用中斷處理句柄的Funcarama2函數(shù)代碼 雖然函數(shù)Funcarama2容易理解,但是仍然難于維護(hù)和修改。 現(xiàn)在讓我們來(lái)利用中斷處理句柄重寫Funcaram1函數(shù),其代碼如清單八。 BOOL Funcarama3 (void) { HANDLE hFile = INVALID_HANDLE_VALUE; LPVOID lpBuf = NULL; __try { DWORD dwNumBytesRead; BOOL fOk; hFile = CreateFile("SOMEDATA.DAT", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) { return (FALSE); } lpBuf = VitualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE); if (lpBuf == NULL) { return (FALSE); } fOk = ReadFile(hFile, lpBuf, 1024, &dwNumBytesRead, NULL); if (!fOk || (dwNumBytesRead == 0)) { VirtualFree(lpBuf, MEM_RELEASE | MEM_DECOMMIT); return (FALSE); } // Do some calculation on the data. . . . } __finally { // Clean up all the resources. if (lpBuf != NULL) VirtualFree(lpBuf, MEM_RELEASE | MEM_DECOMMIT); if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); } // Continue processing. return (TRUE); } 例8 使用了中斷處理句柄的Funcarama3函數(shù)代碼 Funcarama3函數(shù)版的好處是所有的清除工作都集中在一個(gè)地方:finally程序段內(nèi)。這樣在我們需要對(duì)該函數(shù)增加新的條件語(yǔ)句時(shí),我們只需要在finally程序段內(nèi)簡(jiǎn)單增添一行清除語(yǔ)句就可以了,而不必回過(guò)頭來(lái)在每一出可能出錯(cuò)的地方添加清除語(yǔ)句。 Funcarama3函數(shù)的真正問(wèn)題在于其效率。我們以前說(shuō)過(guò)應(yīng)盡可能的避免在try程序段內(nèi)使用return語(yǔ)句。為了避免這種情況,微軟在它的編譯器里引進(jìn)了另一個(gè)關(guān)鍵詞__leave。利用關(guān)鍵詞__leave重寫的Funcarama3函數(shù)見例9。 BOOL Funcarama4 (void) { HANDLE hFile = INVALID_HANDLE_VALUE; LPVOID lpBuf = NULL; // Assume that the function will not execute successfully. BOOL fFunctionOk = FALSE; __try { DWORD dwNumBytesRead; BOOL fOk; hFile = CreateFile("SOMEDATA.DAT", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) { __leave; } lpBuf = VitualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE); if (lpBuf == NULL) { __leave; } fOk = ReadFile(hFile, lpBuf, 1024, &dwNumBytesRead, NULL); if (!fOk || (dwNumBytesRead == 0)) { VirtualFree(lpBuf, MEM_RELEASE | MEM_DECOMMIT); __leave; } // Do some calculation on the data. . . . // Indicate that the entire function executed successfully. fFunctionOk = TRUE; } __finally { // Clean up all the resources. if (lpBuf != NULL) VirtualFree(lpBuf, MEM_RELEASE | MEM_DECOMMIT); if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); } // Continue processing. return (fFunctionOk); } 例9 使用了中斷處理句柄和關(guān)鍵詞__leave的Funcarama4函數(shù)代碼 try程序段內(nèi)的關(guān)鍵詞__leave導(dǎo)致程序運(yùn)行指針直接跳到try程序段的結(jié)尾(你可以將此看成為跳到try程序段的結(jié)束花括弧)。這樣,因?yàn)榭刂屏鞒虒ⅰ白匀弧钡碾x開try程序段,進(jìn)入finally程序段,所以不需付出額外代價(jià)而導(dǎo)致效率降低。但是你需要引進(jìn)一個(gè)新的變量來(lái)指示整個(gè)函數(shù)的運(yùn)行是否成功。 從try程序段到finally程序段,控制流程既可以是自然進(jìn)入,也可以是由于異常的出現(xiàn)而導(dǎo)致控制流程過(guò)早離開try程序段而進(jìn)入finally程序段。為確定何種情況下造成finally程序段的運(yùn)行,我們可以調(diào)用AbnormalTermination函數(shù)來(lái)診斷。 BOOL AbnormalTermination(VOID); 該函數(shù)只能在finally程序段內(nèi)調(diào)用以診斷與此finally相對(duì)應(yīng)的try程序段是否是過(guò)早離開。如果AbnormalTermination的返回值是FALSE,表明程序流程是自然離開try程序段。否則,則是過(guò)早離開。 3、異常處理句柄(Exception handler) 3.1、異常(或例外)處理句柄的定義 異常(或例外)是你不希望出現(xiàn)的事件。在一個(gè)完好的應(yīng)用程序中,你不希望讀寫無(wú)效內(nèi)存地址或除數(shù)為零的情況出現(xiàn)。但是這類錯(cuò)誤的確會(huì)發(fā)生。在出現(xiàn)這類錯(cuò)誤時(shí),CPU會(huì)負(fù)責(zé)提出針對(duì)該類錯(cuò)誤的例外。當(dāng)CPU提出一個(gè)例外時(shí),我們稱之為硬件異常(或例外)(hardware exception)。操作系統(tǒng)和應(yīng)用程序自身也可以提出自己的異常。這類異常我們稱之為軟件異常(或例外)(software exception)。 當(dāng)一個(gè)硬件異;蜍浖惓1惶岢鰰r(shí),操作系統(tǒng)向你的程序提供一種機(jī)會(huì)使得你的程序可以診斷那類異常被提出并允許你的程序?qū)Υ诉M(jìn)行處理。異常處理句柄的語(yǔ)法為 __try { // Guarded body . . . } __except (exception filter) { // Exception handler . . . } 請(qǐng)注意關(guān)鍵詞__except。當(dāng)你建立一個(gè)try程序段時(shí),它必須跟隨一個(gè)finally程序段或一個(gè)except程序段。一個(gè)try程序段不能同時(shí)既跟隨一個(gè)finally程序段又跟隨一個(gè)except程序段。一個(gè)try程序段也不能同時(shí)跟隨多個(gè)finally程序段或多個(gè)except程序段。但是,try-finally程序段卻可以嵌套在try-except程序段內(nèi),或try-except程序段嵌套在try-finally程序段內(nèi)。 3.2、幾個(gè)例子 不同于中斷處理句柄,異常處理句柄直接由操作系統(tǒng)執(zhí)行,編譯器不需要做太多工作。下面我們通過(guò)幾個(gè)具體例子來(lái)討論異常處理句柄是如何工作的。 3.2.1、例5--Funcmeister1函數(shù) 下面是一個(gè)使用了try-except異常處理句柄的函數(shù)Funcmeister1,其代碼見清單十。 DWORD Funcmeister1 (void) { DWORD dwTemp; // 1. Do any processing here. . . . __try { // 2. Perform some operation. dwTemp = 0; } __except (EXCEPTION_EXECUTE_HANDLER) { // 3. Handle an exception; this never executes. . . . } // 3. Continue processing. return (dwTemp); } 例10 例5Funcmeister1函數(shù)代碼 在Funcmeister1函數(shù)中的try程序段內(nèi),我們簡(jiǎn)單地將dwTemp賦值為零。該操作不會(huì)導(dǎo)致任何異常的提出。所以,except程序段內(nèi)的程序永遠(yuǎn)不會(huì)被執(zhí)行。請(qǐng)注意,這有別于中斷處理句柄try-finally。在執(zhí)行了dwTemp賦值語(yǔ)句后的下一個(gè)執(zhí)行語(yǔ)句是return返回語(yǔ)句。 雖然我們不鼓勵(lì)在try程序段內(nèi)使用return, goto, continue和break語(yǔ)句,但是在異常處理句柄的try程序段內(nèi)使用這些語(yǔ)句不會(huì)象中斷處理句柄那樣造成運(yùn)行代碼的增加和效率下降。 3.2.2、例6--Funcmeister2函數(shù) 讓我們對(duì)Funcmeister1函數(shù)進(jìn)行一些改動(dòng),看看會(huì)出現(xiàn)什么情況。改動(dòng)后的函數(shù)見例11。 DWORD Funcmeister2 (void) { DWORD dwTemp = 0; // 1. Do any processing here. . . . __try { // 2. Perform some operation(s). dwTemp = 5 / dwTemp; // Generate an exception dwTemp += 10; // Never excutes } __except ( /* 3. Evaluate filter. */ EXCEPTION_EXECUTE_HANDLER) { // 4. Handle an exception; this never executes. MessageBeep(0); . . . } // 5. Continue processing. return (dwTemp); } 例11 例6Funcmeister2函數(shù)代碼 函數(shù)Funcmeister2中的try程序段dwTemp = 5 / dwTemp語(yǔ)句導(dǎo)致CPU提出一個(gè)硬件異常。當(dāng)該異常被提出時(shí),操作系統(tǒng)會(huì)尋找相對(duì)應(yīng)的except程序段的起始位置并評(píng)估其異常篩選表達(dá)式(exception filter expression)。異常篩選表達(dá)式可以取下列標(biāo)識(shí)符值之一。這些標(biāo)識(shí)符定義在Win32 EXCPT.H頭文件中。 標(biāo)識(shí)符 定義為 EXCEPTION_EXECUTE_HANDLER 1 EXCEPTION_CONTINUE_SEARCH 0 EXCEPTION_CONTINUE_EXECUTION -1 3.3、異常篩選(exception filter) EXCEPTION_EXECUTE_HANDLER表明當(dāng)一個(gè)異常出現(xiàn)時(shí),運(yùn)行程序跳到except程序段轉(zhuǎn)而執(zhí)行except程序段內(nèi)的代碼。except程序段內(nèi)的代碼執(zhí)行完后,系統(tǒng)認(rèn)為該異常已處理完,接著繼續(xù)執(zhí)行except程序段后的代碼。 EXCEPTION_CONTINUE_EXECUTION表明當(dāng)一個(gè)異常出現(xiàn)時(shí),運(yùn)行程序不立即執(zhí)行except程序段內(nèi)的代碼而返回try程序段內(nèi)產(chǎn)生異常的語(yǔ)句繼續(xù)執(zhí)行該語(yǔ)句。 EXCEPTION_CONTINUE_SEARCH表明當(dāng)一個(gè)異常出現(xiàn)時(shí),運(yùn)行程序不執(zhí)行該except程序段內(nèi)的代碼而尋求由高一級(jí)的異常處理句柄來(lái)處理此異常。 Win32 WINBASE.H頭文件中定義了可能出現(xiàn)的各種異常代碼。我們可以通過(guò)調(diào)用GetExceptionCode函數(shù)來(lái)診斷何種異常被提出,從而決定異常處理句柄該采取何種行動(dòng)。GetExceptionCode函數(shù)定義為 DWORD GetExceptionCode(VOID); 它的返回值表明何種異常出現(xiàn)。下面的程序說(shuō)明如何調(diào)用GetExceptionCode函數(shù)。 __try { x = 0; y = 4 / x; } __except ((GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { // Handle divide by zero exception. } 當(dāng)一個(gè)異常發(fā)生時(shí),操作系統(tǒng)會(huì)將有關(guān)該異常的信息儲(chǔ)存在三個(gè)結(jié)構(gòu)中,并將它們存放在提出此異常線程的堆棧里。這三個(gè)結(jié)構(gòu)是EXCEPTION_RECORD,CONTEXT,和EXCEPTION_POINTERS。EXCEPTION_RECORD儲(chǔ)存著與CPU無(wú)關(guān)的異常信息,CONTEXT則儲(chǔ)存著與CPU有關(guān)的異常信息。EXCEPTION_POINTERS結(jié)構(gòu)包含了兩個(gè)分別指向EXCEPTION_RECORD和CONTEXT的指針。 typedef struct _EXCEPTION_POINTERS { PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord; } EXCEPTION_POINTERS 假如你的程序需要這些異常信息,你可以通過(guò)調(diào)用GetExceptionInformation函數(shù)來(lái)獲取。 LPEXCEPTION GetExceptionInformation(void); GetExceptionInformation函數(shù)返回一個(gè)指向EXCEPTION_POINTERS結(jié)構(gòu)的指針。下面的函數(shù)說(shuō)明了如何調(diào)用GetExceptionInformation函數(shù)。 void FuncSkunk (void) { // Declare variables that we can use to save the exception // record and the context if an exception should occur. EXCEPTION_RECORD SavedExceptRec; CONTEXT SavedContext; . . . __try { . . . } __except ( SavedExceptRec = *(GetExceptionInformation())->ExceptionRecord, SavedContext = *(GetExceptionInformation())->ContextRecord, EXCEPTION_EXECUTE_HANDLER) { // We can use the SavedExceptRec and SavedContext // variables inside the handler code block. switch (SavedExceptRec.ExceptionCode) { . . . } } . . . } 注意,在上面的異常篩選表達(dá)式程序中我們使用了C語(yǔ)言的“,”操作符。許多程序員對(duì)此并不是很熟悉。該操作符告訴編譯器從左到右運(yùn)行由“,”分離的各表達(dá)式。在所有的表達(dá)式都運(yùn)行完后,返回最后一個(gè)(或最右面的)表達(dá)式的值。 4、軟件異常(software exception) 至此為止我們所討論的是如何處理由CPU提出的硬件異常(hardware exception)。通常,操作系統(tǒng)或應(yīng)用程序自身提出的軟件異常也非常有用。例如,HeapAlloc函數(shù)就提供了一個(gè)非常好的利用軟件異常的例子。在調(diào)用HeapAlloc時(shí),你可以設(shè)置HEAP_GENERATE-EXCEPTIONS指示旗(flag)。這樣如果HeapAlloc不能滿足你的內(nèi)存分配要求,它會(huì)產(chǎn)生一個(gè)STATUS_NO_MEMORY軟件異常。 假如你想利用這個(gè)異常,你可以在你的try程序段內(nèi)繼續(xù)編寫你的代碼,如同內(nèi)存分配總是會(huì)成功一樣。如果內(nèi)存分配失敗,你可以利用except程序段來(lái)處理這個(gè)異;蚶胒inally程序段來(lái)做清除工作。 你的程序不需要知道你要處理的異常是軟件異常還是硬件異常。你利用try-finally和try-except來(lái)處理軟件異常和硬件異常的方式是一樣的。但是你可以讓你的程序象HeapAlloc函數(shù)一樣提出自己的異常。為了在你的程序中提出軟件異常,你需要調(diào)用RaiseException函數(shù)。 VOID RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD cArguments, LPDWORD lpArguments); 關(guān)于該函數(shù)的使用,請(qǐng)參考微軟的有關(guān)文獻(xiàn)。 5、結(jié)論 結(jié)構(gòu)異常處理由中斷處理和例外處理兩部分組成。采用結(jié)構(gòu)異常處理使得你可以將精力集中在你的程序應(yīng)用代碼設(shè)計(jì)上,從而使得應(yīng)用方案的設(shè)計(jì)更方便、具體。采用結(jié)構(gòu)異常處理編寫的程序更易于理解、修改和維護(hù),從而增加了程序的可讀性和維護(hù)性。
|