轉載請註明出處
作者:小馬
最近越來越多的用到c++的try/catch,發現有很多人錯誤的理解了try/catch的用法,這種誤解我在剛學c++那會其實也有過, 寫篇文章討論一下這個問題,希望大家避免犯這種錯誤.
剛學C++異常處理那會, 我認為try/catch是萬能的, 所謂的"萬能", 是我認為, 加入異常處理的代碼, 我就能很好的處理諸如除數為0,記憶體非法操作等錯誤.舉個例子:
try{ int *p = NULL; *p = 10;}catch (...){ cout <<"catch the exception"<<endl";}
開始我認為我的程式不會崩潰, 雖然有一個非法的記憶體寫操作(往記憶體位址為0位置賦值10), 但是我會catch到,
程式的結果應該是輸出:
catch the exception
後來, 我知道我錯了. 原來try/catch只能捕獲到語言級的異常, 也就是程式中通過throw拋出的異常.舉個例子:
try{ int *p = NULL; throw "this is a exception"}catch (...){ cout <<"catch the exception"<<endl";}
這個程式啟動並執行結果才是輸出:
catch the exception
我想看到這裡,很多人已經明白了. 但是,文章還沒結束, 我要多說幾句.
上面的第一段代碼, 正確的運行結果是什麼呢? 大多數情況下,會彈出一個錯誤對話方塊, 進程結束. 這說明你的程式已經崩潰了. 首先要明確一點,這個異常叫非法記憶體讀寫操作, 既然操作統會彈出對話方塊,說明它已經處理了這個異常(彈出對話方塊,然後強制結束進程,處理方式有點暴力而已).這種異常處理機制,有個名字,叫SEH. 全稱是Structured Exception Handling. 如果解釋什麼是SEH, 可以專門出一篇博文,這裡就不要喧賓奪主了. 簡單的說,它的原理類似於windows的訊息機制, 當程式出一個seh的異常,
通過訊息通知系統, 系統調用異常處理常式來處理你的異常, 針對這篇文章, 你只要瞭解以下幾個資訊就可以了:
1 它是windows作業系統本身的異常處理機制.
2 有以下幾種情況屬於SEH處理的範圍, 比如非法的記憶體讀寫(上面第一段代碼),溢出, 除數為0(上面第二段代碼)等.
在基於C++的項目開發中,很多時候我們希望Structured Exception 也能被try/catch捕獲到, 從而避免程式進程異常結束帶來一些難以預計的結果.
微軟想到了可能會有人有這樣的需求, 所以有一些基於windows的方法把seh和try/catch異常整合, 方法很多,可以在代碼層級上實現整合(比如__try,__except), 也可以直接在IDE工具做一下配置.比如vs2005裡的設定如下:
項目屬性-c/c++-code generation-enable c++ exception裡, 選擇yes with seh exceptions
這樣設定了之後, 在執行上面的程式碼片段1,就會真的輸出:
catch the exception
看完了上面,有些人可能要興奮了: 只要在vs中做一下簡單的設定, 以後就不用擔心程式會崩潰了. 那我就要開始潑冷水了.
我想說的是,我寫這篇文章,旨在分析原理和方法, 並不表示我提倡這樣做, 主要是出於以下幾點,我不提倡在開發工具中通過配置來實現整合,原因有以下幾點:
1混亂,非常之混亂. 在IDE環境裡設定之後, seh和標準的try,catch異常混在一起, 你根本不知道哪裡是要捕獲seh異常,哪裡要捕獲標裝的try catch異常.
2 不利於調試, 在異常整合前, 如果程式在調試狀態執行到上面程式碼片段1的記憶體非法操作處, 會把程式鎖定到出錯的位置,並且告訴你出錯誤的原因. 但整合後,它會跑到catch中, 不利於你發現非法的代碼.
3 不利於代碼的閱讀和理解.
出於以上考慮, 我建議在代碼層級上(比如可以考慮用__try,__except)實現seh與try,catch的結合使用. vc6裡預設把try/catch和seh整合在一起(也就是說用c++的try/catch可以捕獲windows的seh異常), 但到了vs2005就把這個預設選項給去掉了,是不是也說明了什麼呢?
下一篇文章我就打算寫如何在代碼層級上實現seh與標準try/catch的結合.
(不過最近工作比較忙,可能要晚一些了...)