第24章 例外處理常式與軟體異常
本章講解SHE結構的另一個方面的功能。分三個部分:一、異常分類。二、例外處理常式結構。三、與終止處理常式的區別。
一、異常分類:硬體異常和軟體異常。
二、當一個硬體或軟體異常被拋出時,OS會給我們的應用程式一個查看異常類型的機會,並允許應用程式自己處理這個異常。例外處理常式的文法結構:
__try
{//guarded body}
__except(exception filter(異常過濾程式))
{//exception handler(例外處理常式)}
try塊後必須且僅能跟一個except塊或finally塊,不能同時跟倆,多個finally塊或except塊也不行。但卻可以將try-finally塊嵌套與try-except塊中,反過來也可以。
當拋出異常時,系統定位到except塊的開始處,並對異常過濾程式的運算式求值,這個運算式的值必定為以下三個標識符之一:EXCEPTION_EXECUTE_HANDLER、EXCEPTION_EXECUTE_SEARCH、EXCEPTION_EXECUTE_EXECUTION。
講講這三個標識符將怎樣影響線程的執行。參照下面兩個圖。
OK,這兩個圖是從書上截取的。我想說的是,這兩個圖是錯誤的。有興趣的試試這段代碼:
void Func1(){__try{int j = 0;int i = 5 / j;Func2();}__except(EXCEPTION_EXECUTE_HANDLER){AfxMessageBox("Func1!\n");}}void Func2(){int dwTemp = 0;__try{int i = 5;}__finally{AfxMessageBox("Func2!\n");}AfxMessageBox("Func3!\n");}
現在我自己畫正確的圖:
發生EXCEPTION_CONTINUE_EXECUTION異常時,代碼繼續執行,所以最好避免使用此類型的異常。
全域展開,我口述下吧:
情形1:try-except塊外麵包含著try-except塊:若異常發生在外層try-except塊,則內層try-except塊不執行;若異常發生在內層的try-except塊,則執行完內層後接著下一行執行代碼。
情形2:try-finally塊外麵包含著try-except塊,且異常發生在try-except塊:當異常發生時,若try-finally塊還未執行,則僅執行except塊。其實類似情況1。
情形3(也是最複雜的情況):try-finally塊外麵包含著try-except塊,且異常發生在try-finally塊。執行順序:先執行try-finally塊的finally,然後再執行try-except的except塊。若異常發生在try-except塊,則finally塊就不執行。
注意:自Windows Vista開始,若一個異常發生在try/finally塊,而其外層有沒有try/except塊(同時過濾程式返回EXCEPTION_CONTINUE_HANDLER),進程就立刻終止,不再全域展開也不執行finally塊。
試試下面的,看能算出正確的執行順序嗎?答案見代碼後。
void Func1(){//1.do any processing here.__try{//2.call another function.Func2();}__except(/*6.Evaluate filter*/EXCEPTION_EXECUTE_HANDLE){//8.After the unwind, the exception handler executes.}//9.exception handled---continue execution.}void Func2(){DWORD dwTemp = 0;//3.do any processing here.__try{//4.request permission to access protected data.WaitForSingleObject(g_hSem, INFINITE);//5.modify the data//an exception is generated here.g_dwProtectedData = 5 / dwTemp;}__finally{//7.global unwind occurs because filter evaluated to EXCETPTION_EXECUTE_HANDLER.//allow others to use protected data.ReleaseSemaphore(g_hSem, 1, NULL);}//continue processing--never executes.}
答案:上面的數字表明了執行順序。
GetExceptionCode是內在函數,它的傳回值表明剛剛發生的異常的類型。此函數只能在異常過濾程式裡(即__except之後的括弧裡)或例外處理常式的代碼裡調用。
逗號操作符:從左至右執行逗號分隔的運算式,當所有運算式都求值完畢時,返回最後一個運算式的值。
軟體異常:我們可在程式裡強制拋出一個異常,而非由CPU捕獲某個事件拋出的異常。傳統的做法是:函數通過返回一些錯誤碼來指明運行失敗。函數的調用者應檢查這些錯誤碼並採取相應的措施。這會導致調用者需要頻繁的做清理工作並返回給它自己的調用者一個失敗代碼。錯誤碼的逐層傳播導致代碼很難編寫和維護。
另一種方法是我們可讓函數在失敗時拋出異常,而不是返回錯誤碼。這種方式,代碼可易編寫和維護。且因省略了很多的錯誤檢查代碼,程式執行效率更高。RaiseException可拋出一個異常。
軟體異常的捕獲方式與硬體異常完全一樣。參考P660。
三、與終止處理常式區別:①異常過濾程式(exception filter)和例外處理常式(exception handler)主要由OS來負責執行-----在異常過濾程式運算式計算和例外處理常式執行方面,編譯器所做的工作十分有限。②還記得在終止處理常式try塊中建議不用return,continue,break等語句,但在例外處理常式的try塊中,這些語句不會導致程式效能損失或增加代碼量。