C++及Windows異常處理(try,catch; __try,__finally; __try, __except)——一道筆試題引起的探究

來源:互聯網
上載者:User

題目:

    int*   p   =   0x00000000;       //   pointer   to   NULL     puts( "hello ");     __try{         puts( "in   try ");         __try{             puts( "in   try ");             *p   =   13;         //   causes   an   access   violation   exception;         }__finally{             puts( "in   finally ");         }     }__except(puts( "in   filter "),   1){         puts( "in   except ");     }     puts( "world ");     /*    hello    in try    in try    in filter    in finally    in except    world    */

上面的題目,我把答案列了出來。

常用C++的朋友,應該沒見過__try這種形式的語句,下面我把try,catch; __try,__finally; __try, __except這三對異常處理使用標示逐一說明

  • C++ 異常處理:try,catch
try{    // 可能出錯的語句    // 如果有錯,就——    throw ... // 初始化一個異常對象(exception object) }catch( 類型名 [形參名] ) /* 異常說明符(exception specifier)*/ {  } catch( 類型名 [形參名] ) { }

C++的異常處理很簡單,就是如上的三個關鍵字,注意C++中throw,catch之後沒有Java等語言中的finally。

Q: 為何C++不提供“finally”結構?
A: 因為C++提供了另一種機制,完全可以取代finally,而且這種機制幾乎總要比finally工作得更好:就是——“分配資源即初始化”。(見《The C++ Programming Language》14.4節)基本的想法是,用一個局部對象來封裝一個資源,這樣一來局部對象的解構函式就可以自動釋放資源。這樣,程式員就不會“忘記釋放資源”了。 [譯註:因為C++的對象“生命週期”機制替他記住了 :O) ] 下面是一個例子:

class File_handle {        FILE* p;    public:        File_handle(const char* n, const char* a)            { p = fopen(n,a); if (p==0) throw Open_error(errno); }        File_handle(FILE* pp)            { p = pp; if (p==0) throw Open_error(errno); }        ~File_handle() { fclose(p); }        operator FILE*() { return p; }        // ...    };    void f(const char* fn)    {        File_handle f(fn,"rw");    // open fn for reading and writing        // use file through f    }

在一個系統中,每一樣資源都需要一個“資源局柄”對象,但我們不必為每一個資源都寫一個“finally”語句。在實作的系統中,資源的擷取和釋放的次數遠遠多於資源的種類,所以“資源分派即初始化”機制產生的代碼要比“finally”機制少。

 

好了,接下來,看另外兩組異常模型機制,它們是Windows系列作業系統平台上提供的SEH模型,也就是說在C++中調用的時候,其實是調用Windows的API

SEH,又稱結構化異常處理.是設計Windows作業系統時提出一個種處理異常的方法。

  • __try, __except

這組異常處理機制和C++的很相像,只是關鍵字是except而不是catch

catch 和 except 的一點不同: catch關鍵字後面往往好像接受一個函數參數一樣,可以是各種類型的異常資料對象;但是__except關鍵字則不同,它後面跟的卻是一個運算式(可以是各種類型的運算式)

下面是一個例子:

void main(){    puts("hello");    // 定義受監控的代碼模組    __try    {        puts("in try");    }    //定義異常處理模組    __except(1)    {        puts("in except");    }    puts("world");}

1. 受監控的代碼模組被執行(也即__try定義的模組代碼);
2. 如果上面的代碼執行過程中,沒有出現異常的話,那麼控制流程將轉入到__except子句之後的代碼模組中;
3. 否則,如果出現異常的話,那麼控制流程將進入到__except後面的運算式中,也即首先計算這個運算式的值,之後再根據這個值,來決定做出相應的處理。

EXCEPTION_CONTINUE_EXECUTION (–1) 異常被忽略,控制流程將在異常出現的點之後,繼續恢複運行。
EXCEPTION_CONTINUE_SEARCH (0) 異常不被識別,也即當前的這個__except模組不是這個異常錯誤所對應的正確的異常處理模組。系統將繼續到上一層的try-except域中繼續尋找一個恰當的__except模組。
EXCEPTION_EXECUTE_HANDLER (1) 異常已經被識別,也即當前的這個異常錯誤,系統已經找到了並能夠確認,這個__except模組就是正確的異常處理模組。控制流程將進入到__except模組中。

小結:

(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模型中,尋找搜尋異常模組的規則也是逐級向上進行的。但是稍有區別的是,C++異常模型是按照異常對象的類型來進行匹配尋找的;而try-except模型則不同,它通過一個運算式的值來進行判斷。如果運算式的值為1(EXCEPTION_EXECUTE_HANDLER),表示找到了異常處理模組;如果值為0(EXCEPTION_CONTINUE_SEARCH),表示繼續向上一層的try-except域中繼續尋找其它可能匹配的異常處理模組;如果值為-1(EXCEPTION_CONTINUE_EXECUTION),表示忽略這個異常,注意這個值一般很少用,因為它很容易導致程式難以預測的結果,例如,死迴圈,甚至導致程式的崩潰等。

(5) __except關鍵字後面跟的運算式,它可以是各種類型的運算式,例如,它可以是一個函數調用,或是一個條件運算式,或是一個逗號運算式,或乾脆就是一個整型常量等等。最常用的是一個函數運算式,並且通過利用GetExceptionCode()或GetExceptionInformation ()函數來擷取當前的異常錯誤資訊,便於程式員有效控制異常錯誤的分類處理。

(6) SEH異常處理模型中,異常被劃分為兩大類:系統異常和軟體異常。其中軟體異常通過RaiseException()函數拋出。RaiseException()函數的作用類似於C++異常模型中的throw語句。

  • __try, __finally

try-finally語句的文法與try-except很類似,稍有不同的是,__finally後面沒有一個運算式,這是因為try- finally語句的作用不是用於異常處理,所以它不需要一個運算式來判斷當前異常錯誤的種類。另外,與try-except語句類似,try- finally也可以是多層嵌套的,並且一個函數內可以有多個try-finally語句,不管它是嵌套的,或是平行的。當然,try-finally多層嵌套也可以是跨函數的。

最關鍵的一點: “不管在何種情況下,在離開當前的範圍時,finally塊地區內的代碼都將會被執行到”

void tmain(){    puts("hello");    __try    {        puts("__try塊中");        // 注意,下面return語句直接讓函數返回了        return;    }    __finally    {        puts("__finally塊中");    }    puts("world");}

上面的程式運行結果如下:
hello
__try塊中
__finally塊中
Press any key to continue

 

小結:

__finally塊被執行的流程時,無外乎三種情況。

第一種就是順序執行到__finally塊地區內的代碼,這種情況很簡單,容易理解;

第二種就是goto語句或return語句引發的程式控制流程離開當前__try塊範圍時,系統自動完成對__finally塊代碼的調用;

第三種就是由於在__try塊中出現異常時,導致程式控制流程離開當前__try塊範圍,這種情況下也是由系統自動完成對__finally塊的調用。

無論是第 2種,還是第3種情況,毫無疑問,它們都會引起很大的系統開銷,編譯器在編譯此類程式碼時,它會為這兩種情況準備很多的額外代碼。

一般第2種情況,被稱為“局部展開(LocalUnwinding)”;第3種情況,被稱為“全域展開(GlobalUnwinding)”。在後面闡述SEH實現的時候會詳細分析到這一點。

第3種情況,也即由於出現異常而導致的“全域展開”,對於程式員而言,這也許是無法避免的,因為你在利用異常處理機制提高程式可靠健壯性的同時,不可避免的會引起效能上其它的一些開銷。呵呵!這世界其實也算瞞公平的,有得必有失。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.