《談談windows核心編程系列》 結構化異常處理SEH之__finally終止處理

來源:互聯網
上載者:User

        結構化異常處理SEH:__finally終止處理。

 

 結構化異常處理(Structuredexception handling)簡稱SEH。是windows系統提供的異常處理機制。促使windows將SEH加入到windows系統的一個關鍵原因就是:它可以簡化作業系統本身的開發工作,同時還讓系統更加健壯。

我們當然也可以在我們的程式中添加SEH機制,這樣我們的應用程式也可以變得更加健壯。使用SEH,我們在編寫代碼時可以先集中精力完成軟體的正常工作流程。也就是說將軟體主要功能編寫和軟體異常處理這兩個任務分離開,最後再去處理軟體可能遇到的各種錯誤情況。

為了實現SEH,編譯器完成了很多的工作。在進入和離開異常處理代碼時編譯器會插入一些額外的代碼,有時這會導致很大的開銷。後面的文章我會介紹編譯器都是做了哪些工作。

雖然不同的廠商按照不同的方式來實現SEH,但是大部分的編譯廠商都遵循了microsoft的文法規則。因此本文我們將介紹Microsoft visualC++編譯器規定的文法。

SEH包括兩個方面的內容:終止處理和異常處理。本文我們介紹終止處理,下一篇文章將介紹異常處理。

 

終止處理

終止處理常式確保無論一個代碼塊是如何退出的,都能保證該終止處理常式能夠被執行。其文法如下:

__try{   //被保護的代碼。}__finally//終止處理。{   //終止處理代碼。}

注意:try和finally前都有兩個底線。

終止程式也分為兩個部分:__try塊為中止處理常式要保護的代碼。無論程式以何種方式從該塊中退出(如return、goto等語句),__finally塊都會被執行。

__finally塊為終止處理常式塊,該塊中的代碼會在控制流程從__try塊退出後、程式結束之前被調用。一般用來執行一些清理操作,如釋放資源、釋放佔有的互斥量等。

但是上面所說的”無論何種方式“有些絕對。因為這個世界本來就沒有絕對的東西。當在try塊中使用了ExitProcess、ExitThread、TerminateProcess、TerminateThread來終止線程或進程時,finally塊就不會被調用。要特別注意這些例外,禁止在try塊中使用這些函數。

下面通過代碼給大家講解終止處理常式的使用:

__try{    WaitForSingleObject(hMutex,INFINITE);    if(x==false)       return-1;}__finally{          ReleaseMutex(hMutex);      return -2;}return 0;

可以看到上面的代碼在try中首先等待互斥量核心對象被觸發,等待成功後互斥量就屬於該線程所有。如果此時執行return-1,則該線程結束,就會導致互斥量對象一直被佔有的情況的發生,其他線程會一直處於等待狀態。這就是所謂的資源泄漏。有了finally塊的保護後,當執行return-1程式試圖退出try塊時,編譯器會讓finally塊在return之前執行,同時在return(其他語句,如goto等語句也一樣)語句之前插入一些代碼。return的傳回值會被儲存在一個臨時變數中。

但上面的代碼程式的傳回值是多少呢?是-1,還是-2呢?答案是-2。當編譯器在try塊中檢測到return語句時,雖然會產生一些代碼將傳回值儲存在一個臨時變數中。但是由於finally塊代碼中也有一個return,finally塊中return的值會將原來的傳回值覆蓋。所以函數最終返回-2。

這個過程被稱為局部展開。局部展開會在系統因為try塊中的代碼提前退出時發生。局部展開會導致非常大的額外開銷,因為編譯器必須插入代碼來保證finally塊在程式退出之前執行。最理想的情況就是代碼控制流程正常的離開try塊而進入到finally塊中,這時的額外開銷最小。在x86體系下離開try塊正常進入到finally塊只需要執行一條指令。

因此為了將效能開銷降到最低,我們改進了上面的代碼:

__try{      WaitForSingleObject(hMutex,INFINITE);    if(x==false)     gotoEndOfTryBlock;   //一些代碼。  EndOfTryBlock:}__finally{   ReleaseMutex(hMutex);    return -2;}return 0;

在上面改進後的代碼中,當在try塊中檢查到錯誤發生時不再直接調用return強制退出。而是執行了一條goto語句到try塊的末尾。執行此跳轉後,控制流程從try塊中正常退出,直接進入到finally塊中。由於沒有發生局部展開,編譯器不需要插入額外指令,也就沒有導致額外的開銷。

 

由於goto語句會破壞程式的執行流程,很多書上都再三強調禁止使用goto。其實我們也沒有必要使用goto,因為microsoft提供給我們一個關鍵字_leave,也可以執行類似的操作。關鍵字_leave會導致代碼執行控制流程跳轉到try塊的末尾,從而代碼將正常的從try塊進入到finally塊中。

下面為使用__leave關鍵字的改進代碼:

__try{      WaitForSingleObject(hMutex,INFINITE);    if(x==false)    __leave   //一些代碼。}__finally{   ReleaseMutex(hMutex);    return -2;}return 0; 

本章開頭曾介紹過使用SEH可以讓程式員將程式正常執行流程和錯誤處理分開。下面我們分別展示兩個例子,一個沒有使用SEH,而另一個使用SEH,看下它們到底有何差別,通過比較我們也可以更好的知道SEH是如何完成上述工作的。

下面是沒有使用SEH的程式:

bool fun(char* fileName){      HANDLEhFile=CreateFile(....);      if(hFile==INVALID_HANDLE_VALUE)      {           returnfalse;      }      HANDLEhFileMapping=CreateFileMapping(...);      if(hFile==NULL)      {           CloseHandle(hFile);           returnfalse;       }      char*p=MapViewOfFile(..);      if(p==NULL)      {           CloseHandle(hFile);           CloseHandle(hFileMapping);            return false;      }       //其他工作.......      returntrue;}

 

相信我們很多人都寫過類似上面的代碼。我們可以看到,上面的代碼中包含了很多錯誤檢查和資源清理的代碼。過多的錯誤碼檢查和資源清理工作會使得代碼難以閱讀,同時也難以編寫、修改和維護。

現在讓我們來通過使用SEH機制改進上面的代碼:

bool fun(char* fileName){      HANDLEhFile=INVALID_HANDLE_VALUE;      HANDLEhFileMapping=NULL;      char*p=NULL;      __try      {           hFile=CreateFile(....);           if(hFile==INVALID_HANDLE_VALUE)                 __leave;            hFileMapping=CreateFileMapping(...);           if(hFile==NULL)                 __leave;                   p=MapViewOfFile(..);           if(p==NULL)                 __leave;      //其他代碼。      }      __finally      {           if(hFile!=INVALID_HANDLE_VALUE)                 CloseHandle(hFile);           if(hFileMapping)                 CloseHandle(hFileMapping);       }      returntrue;}

從上面的代碼我們可以看到代碼簡潔多了。清理工作放在最後執行,且能夠保證能得到執行,代碼看起來簡潔而有序,提高了可讀性也有利於以後的維護。

注意事項:前面我們介紹了兩種會引起finally塊執行的情形:

一:從try塊正常退出,進入到finally塊。

二:局部展開:從try塊中提前退出,將程式控制流程強制轉到finally塊。

除了上面的情況外,還有一種情況:全域展開,也會導致finally塊被執行。關於全域展開由於涉及到異常處理的內容,我們將在下一篇文章中詳細介紹!

finally的執行都是由上面三種情況之一造成的。

如果要確定到底正常進入finally還是異常退出try塊退出,可以調用AbnormalTermination函數來判斷:

BOOL AbnormalTermination();


注意只能在finally塊中調用該函數。當返回true時,表示控制流程從try塊中正常進入到finally塊中。返回false時,則表示控制流程從try塊中異常退出,通常是由於執行goto、break、return或continue語句而導致局部展開,或是try塊中的代碼拋出異常而引起全域展開所致。


                                                       如有紕漏,請指正!謝謝!

                                                                    2013、3、11於浙江杭州

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.