色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

C++的異常機制底層原理與實際應用詳細說明

Wildesbeast ? 來源:21IC ? 作者:21IC ? 2020-11-22 11:34 ? 次閱讀

我們在對 vector 做 push 操作的時候,或者對某個指針做 new 操作的時候,如果沒有做異常處理,一旦系統內存不夠用了,程序是會被 terminate 掉的。這就要求我們熟悉 C++ 異常,保證日常開發中能正確處理它。本文主要介紹C++ 異常機制的底層原理與實際應用,通俗易懂,快來讀一讀吧。

以下是正文

C++異常機制概述

異常處理是C++的一項語言機制,用于在程序中處理異常事件。異常事件在 C++ 中表示為 異常對象 。異常事件發生時,程序使用throw關鍵字拋出異常表達式,拋出點稱為異常出現點,由操作系統為程序設置當前異常對象,然后執行程序的當前異常處理代碼塊,在包含了異常出現點的最內層的 try 塊,依次匹配catch語句中的異常對象(只進行類型匹配,catch參數有時在 catch 語句中并不會使用到)。若匹配成功,則執行 catch 塊內的異常處理語句,然后接著執行 try.。.catch.。. 塊之后的代碼。如果在當前的 try.。.catch.。. 塊內找不到 匹配 該異常對象的catch語句,則由更外層的 try.。.catch.。. 塊來處理該異常;如果當前函數內所有的 try.。.catch.。. 塊都不能匹配該異常,則遞歸回退到調用棧的上一層去處理該異常。如果一直退到主函數 main() 都不能處理該異常,則調用系統函數 terminate() 終止程序。

一個最簡單的 try.。.catch.。. 的例子如下所示。我們有個程序用來記班級學生考試成績,考試成績分數的范圍在 0-100 之間,不在此范圍內視為數據異常:

int main(){ int score=0; while (cin 》》 score) { try { if (score 》 100 || score 《 0) { throw score; } //將分數寫入文件或進行其他操作 } catch (int score) { cerr 《《 “你輸入的分數數值有問題,請重新輸入!”; continue; } }}

throw 關鍵字

在上面這個示例中, throw 是個關鍵字,與拋出表達式構成了 throw 語句。 其語法為:

throw 表達式;

throw 語句必須包含在 try 塊中,也可以是被包含在調用棧的外層函數的 try 塊中,如:

//示例代碼:throw包含在外層函數的try塊中void registerScore(int score){ if (score 》 100 || score 《 0) throw score; //throw語句被包含在外層main的try語句塊中 //將分數寫入文件或進行其他操作}int main(){ int score=0; while (cin 》》 score) { try { registerScore(score); } catch (int score) { cerr 《《 “你輸入的分數數值有問題,請重新輸入!”; continue; } }}

執行 throw 語句時,throw 表達式將作為對象被復制構造為一個新的對象,稱為異常對象。 異常對象放在內存的特殊位置,該位置既不是棧也不是堆,在 window 上是放在線程信息塊 TIB 中。 這個構造出來的新對象與本級的 try 所對應的 catch 語句進行 類型匹配 ,類型匹配的原則在下面介紹。

在本例中,依據 score 構造出來的對象類型為 int,與 catch(int score) 匹配上,程序控制權轉交到 catch 的語句塊,進行異常處理代碼的執行。如果在本函數內與 catch 語句的類型匹配不成功,則在調用棧的外層函數繼續匹配,如此遞歸執行直到匹配上 catch 語句,或者直到 main 函數都沒匹配上而調用系統函數 terminate() 終止程序。當執行一個 throw 語句時,跟在 throw 語句之后的語句將不再被執行,throw 語句的語法有點類似于 return,因此導致在調用棧上的函數可能提早退出。

異常對象

異常對象是一種特殊的對象,編譯器依據異常拋出表達式復制構造異常對象,這要求拋出異常表達式不能是一個不完全類型(一個類型在聲明之后定義之前為一個不完全類型。不完全類型意味著該類型沒有完整的數據與操作描述),而且可以進行復制構造,這就要求異常拋出表達式的復制構造函數(或移動構造函數)、析構函數不能是私有的。

異常對象不同于函數的局部對象,局部對象在函數調用結束后就被自動銷毀,而異常對象將駐留在所有可能被激活的 catch 語句都能訪問到的內存空間中,也即上文所說的 TIB。當異常對象與 catch 語句成功匹配上后,在該 catch 語句的結束處被自動析構。在函數中返回局部變量的引用或指針幾乎肯定會造成錯誤,同樣的道理,在 throw 語句中拋出局部變量的指針或引用也幾乎是錯誤的行為。如果指針所指向的變量在執行 catch 語句時已經被銷毀,對指針進行解引用將發生意想不到的后果。throw 出一個表達式時,該表達式的靜態編譯類型將決定異常對象的類型。所以當 throw 出的是基類指針的解引用,而該指針所指向的實際對象是派生類對象,此時將發生派生類對象切割。除了拋出用戶自定義的類型外,C++ 標準庫定義了一組類,用戶報告標準庫函數遇到的問題。這些標準庫異常類只定義了幾種運算,包括創建或拷貝異常類型對象,以及為異常類型的對象賦值。標準異常類描述頭文件

exception最通用的異常類,只報告異常的發生而不提供任何額外的信息exception

runtime_error只有在運行時才能檢測出的錯誤stdexcept

rang_error運行時錯誤:產生了超出有意義值域范圍的結果stdexcept

overflow_error運行時錯誤:計算上溢stdexcept

underflow_error運行時錯誤:計算下溢stdexcept

logic_error程序邏輯錯誤stdexcept

domain_error邏輯錯誤:參數對應的結果值不存在stdexcept

invalid_argument邏輯錯誤:無效參數stdexcept

length_error邏輯錯誤:試圖創建一個超出該類型最大長度的對象stdexcept

out_of_range邏輯錯誤:使用一個超出有效范圍的值stdexcept

bad_alloc內存動態分配錯誤new

bad_castdynamic_cast類型轉換出錯type_info

catch 關鍵字

catch語句匹配被拋出的異常對象。如果 catch 語句的參數是引用類型,則該參數可直接作用于異常對象,即參數的改變也會改變異常對象,而且在 catch 中重新拋出異常時會繼續傳遞這種改變。如果 catch 參數是傳值的,則復制構函數將依據異常對象來構造catch 參數對象。在該 catch 語句結束的時候,先析構 catch 參數對象,然后再析構異常對象。

在進行異常對象的匹配時,編譯器不會做任何的隱式類型轉換或類型提升。除了以下幾種情況外,異常對象的類型必須與 catch 語句的聲明類型完全匹配:

允許從非常量到常量的類型轉換。

允許派生類到基類的類型轉換。

數組被轉換成指向數組(元素)類型的指針。

函數被轉換成指向函數類型的指針。

尋找 catch 語句的過程中,匹配上的未必是類型完全匹配那項,而在是最靠前的第一個匹配上的 catch 語句(我稱它為最先匹配原則)。所以,派生類的處理代碼 catch 語句應該放在基類的處理 catch 語句之前,否則先匹配上的總是參數類型為基類的 catch 語句,而能夠精確匹配的 catch 語句卻不能夠被匹配上。在 catch 塊中,如果在當前函數內無法解決異常,可以繼續向外層拋出異常,讓外層catch 異常處理塊接著處理。此時可以使用不帶表達式的 throw 語句將捕獲的異常重新拋出:

catch(type x){ //做了一部分處理 throw;}

被重新拋出的異常對象為保存在 TIB 中的那個異常對象,與 catch 的參數對象沒有關系,若 catch 參數對象是引用類型,可能在 catch 語句內已經對異常對象進行了修改,那么重新拋出的是修改后的異常對象; 若catch參數對象是非引用類型,則重新拋出的異常對象并沒有受到修改。使用 catch(。..){} 可以捕獲所有類型的異常,根據最先匹配原則,catch(。..){} 應該放在所有 catch 語句的最后面,否則無法讓其他可以精確匹配的 catch 語句得到匹配。通常在catch(。..){} 語句中執行當前可以做的處理,然后再重新拋出異常。注意,catch 中重新拋出的異常只能被外層的 catch 語句捕獲。

棧展開、RAII

其實棧展開已經在前面說過,就是從異常拋出點一路向外層函數尋找匹配的 catch 語句的過程,尋找結束于某個匹配的 catch 語句或標準庫函數 terminate。這里重點要說的是棧展開過程中對局部變量的銷毀問題。我們知道,在函數調用結束時,函數的局部變量會被系統自動銷毀,類似的,throw 可能會導致調用鏈上的語句塊提前退出,此時,語句塊中的局部變量將按照構成生成順序的逆序,依次調用析構函數進行對象的銷毀。例如下面這個例子:

//一個沒有任何意義的類class A{public: A() :a(0){ cout 《《 “A默認構造函數” 《《 endl; } A(const A& rsh){ cout 《《 “A復制構造函數” 《《 endl; } ~A(){ cout 《《 “A析構函數” 《《 endl; }private: int a;};int main(){ try { A a ; throw a; } catch (A a) { ; } return 0;}

程序將輸出:

定義變量 a 時調用了默認構造函數,使用 a 初始化異常變量時調用了復制構造函數,使用異常變量復制構造 catch 參數對象時同樣調用了復制構造函數。三個構造對應三個析構,也即 try 語句塊中局部變量 a 自動被析構了。然而,如果 a 是在自由存儲區上分配的內存時:

int main(){ try { A * a= new A; throw *a; } catch (A a) { ; } getchar(); return 0;}

程序運行結果:

同樣的三次構造,卻只調用了兩次的析構函數!說明 a 的內存在發生異常時并沒有被釋放掉,發生了內存泄漏。

RAII機制有助于解決這個問題,RAII(Resource acquisition is initialization,資源獲取即初始化)。它的思想是以對象管理資源。為了更為方便、魯棒地釋放已獲取的資源,避免資源死鎖,一個辦法是把資源數據用對象封裝起來。程序發生異常,執行棧展開時,封裝了資源的對象會被自動調用其析構函數以釋放資源。C++ 中的智能指針便符合RAII。關于這個問題詳細可以看《Effective C++》條款13.異常機制與構造函數

異常機制的一個合理的使用是在構造函數中。構造函數沒有返回值,所以應該使用異常機制來報告發生的問題。更重要的是,構造函數拋出異常表明構造函數還沒有執行完,其對應的析構函數不會自動被調用,因此析構函數應該先析構所有所有已初始化的基對象,成員對象,再拋出異常。

C++ 類構造函數初始化列表的異常機制,稱為 function-try block。一般形式為:

myClass::myClass(type1 pa1) try: _myClass_val (初始化值){ /*構造函數的函數體 */} catch ( exception& err ){ /* 構造函數的異常處理部分 */};

異常機制與析構函數C++ 不禁止析構函數向外界拋出異常,但析構函數被期望不向外界函數拋出異常。析構函數中向函數外拋出異常,將直接調用 terminator() 系統函數終止程序。如果一個析構函數內部拋出了異常,就應該在析構函數的內部捕獲并處理該異常,不能讓異常被拋出析構函數之外??梢匀绱颂幚恚?/p>

若析構函數拋出異常,調用 std::abort() 來終止程序。

在析構函數中 catch 捕獲異常并作處理。

關于具體細節,有興趣可以看《Effective C++》條款08:別讓異常逃離析構函數。

noexcept修飾符與noexcept操作符

noexcept 修飾符是 C++11 新提供的異常說明符,用于聲明一個函數不會拋出異常。編譯器能夠針對不拋出異常的函數進行優化,另一個顯而易見的好處是你明確了某個函數不會拋出異常,別人調用你的函數時就知道不用針對這個函數進行異常捕獲。在 C++98中關于異常處理的程序中你可能會看到這樣的代碼:

void func() throw(int ,double ) {。..}void func() throw(){。..}

這是 throw 作為函數異常說明,前者表示 func()這個函數可能會拋出 int 或 double 類型的異常,后者表示 func() 函數不會拋出異常。事實上前者很少被使用,在 C++11 這種做法已經被摒棄,而后者則被 C++11 的 noexcept 異常聲明所代替:

void func() noexcept {。..}//等價于void func() throw(){。..}

在 C++11 中,編譯器并不會在編譯期檢查函數的 noexcept 聲明,因此,被聲明為noexcept 的函數若攜帶異常拋出語句還是可以通過編譯的。 在函數運行時若拋出了異常,編譯器可以選擇直接調用 terminate() 函數來終結程序的運行,因此,noexcept 的一個作用是 阻止異常的傳播,提高安全性 。上面一點提到了,我們不能讓異常逃出析構函數,因為那將導致程序的不明確行為或直接終止程序。實際上出于安全的考慮,C++11 標準中讓類的析構函數默認也是 noexcept 的。同樣是為了安全性的考慮,經常被析構函數用于釋放資源的 delete 函數,C++11 也默認將其設置為 noexcept。

noexcept也可以接受一個常量表達式作為參數,例如:

void func() noexcept(常量表達式);

常量表達式的結果會被轉換成 bool 類型,noexcept(bool) 表示函數不會拋出異常,noexcept(false) 則表示函數有可能會拋出異常。 故若你想更改析構函數默認的 noexcept聲明,可以顯式地加上 noexcept(false) 聲明,但這并不會帶給你什么好處。

異常處理的性能分析

異常處理機制的主要環節是運行期類型檢查。當拋出一個異常時,必須確定異常是不是從 try 塊中拋出。異常處理機制為了完善異常和它的處理器之間的匹配,需要存儲每個異常對象的類型信息以及 catch 語句的額外信息。由于異常對象可以是任何類型(如用戶自定義類型),并且也可以是多態的,獲取其動態類型必須要使用運行時類型檢查(RTTI),此外還需要運行期代碼信息和關于每個函數的結構。當異常拋出點所在函數無法解決異常時,異常對象沿著調用鏈被傳遞出去,程序的控制權也發生了轉移。轉移的過程中為了將異常對象的信息攜帶到程序執行處(如對異常對象的復制構造或者 catch 參數的析構),在時間和空間上都要付出一定的代價,本身也有不安全性,特別是異常對象是個復雜的類的時候。異常處理技術在不同平臺以及編譯器下的實現方式都不同,但都會給程序增加額外的負擔,當異常處理被關閉時,額外的數據結構、查找表、一些附加的代碼都不會被生成,正是因為如此,對于明確不拋出異常的函數,我們需要使用 noexcept 進行聲明。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 內存
    +關注

    關注

    8

    文章

    3047

    瀏覽量

    74207
  • 函數
    +關注

    關注

    3

    文章

    4344

    瀏覽量

    62864
  • C++
    C++
    +關注

    關注

    22

    文章

    2114

    瀏覽量

    73780
收藏 人收藏

    評論

    相關推薦

    求3525電路詳細說明

    求3525電路詳細說明,越詳細越好,謝謝!
    發表于 04-18 08:21

    F28335的SCI的詳細說明哪里可以找

    到什么地方怎樣找。比如我需要F28335的SCI的詳細說明,我需要F28335的I2C詳細說明(具體到工作原理的細節,每一個寄存器每一個位的說明),不一定要中文的,英文的也可以,請問
    發表于 06-10 15:31

    C++異常機制探討

    C++異常機制為我們提供了更好的解決方法。異常處理的基本思想是:當出現錯誤時拋出一個異常,希望它的調用者能捕獲并處理這個
    發表于 11-23 11:04 ?3480次閱讀
    <b class='flag-5'>C++</b><b class='flag-5'>異常</b><b class='flag-5'>機制</b>探討

    C++的cast最完整最詳細的解釋資料說明

    本文檔的主要內容詳細介紹的是C++的cast最完整最詳細的解釋資料說明
    發表于 01-29 15:26 ?0次下載
    <b class='flag-5'>C++</b>的cast最完整最<b class='flag-5'>詳細</b>的解釋資料<b class='flag-5'>說明</b>

    C++程序設計教程之函數機制詳細資料說明

    本文檔詳細介紹的是C++程序設計教程之函數機制詳細資料說明主要內容包括了: 1.函數性質( Function Character ) ,
    發表于 02-22 11:24 ?2次下載
    <b class='flag-5'>C++</b>程序設計教程之函數<b class='flag-5'>機制</b>的<b class='flag-5'>詳細</b>資料<b class='flag-5'>說明</b>

    C++程序設計教程之C++的初步知識的詳細資料說明

    C++程序設計教程之C++的初步知識的詳細資料說明包括了:1. 從CC++,2 . 最簡單的
    發表于 03-14 14:48 ?31次下載
    <b class='flag-5'>C++</b>程序設計教程之<b class='flag-5'>C++</b>的初步知識的<b class='flag-5'>詳細</b>資料<b class='flag-5'>說明</b>

    C++程序設計教程之C++工具的詳細資料說明

    本文檔的詳細介紹的是C++程序設計教程之C++工具的詳細資料說明主要內容包括了:1. 異常處理,
    發表于 03-14 16:39 ?4次下載
    <b class='flag-5'>C++</b>程序設計教程之<b class='flag-5'>C++</b>工具的<b class='flag-5'>詳細</b>資料<b class='flag-5'>說明</b>

    I2C總線的規范詳細說明

    本文檔的主要內容詳細介紹的是I2C總線的規范詳細說明
    發表于 09-30 17:29 ?18次下載
    I2<b class='flag-5'>C</b>總線的規范<b class='flag-5'>詳細說明</b>

    C++語言編碼規范詳細說明

    本文檔的主要內容詳細介紹的是C++語言編碼規范詳細說明
    發表于 01-07 16:19 ?14次下載
    <b class='flag-5'>C++</b>語言編碼規范<b class='flag-5'>詳細說明</b>

    C語言程序設計的復習資料詳細說明

    本文檔的主要內容詳細介紹的是C語言程序設計的復習資料詳細說明。
    發表于 03-26 16:46 ?3次下載
    <b class='flag-5'>C</b>語言程序設計的復習資料<b class='flag-5'>詳細說明</b>

    C語言的拓展歸納總結詳細說明

    本文檔的主要內容詳細介紹的是C語言的拓展歸納總結詳細說明
    發表于 07-29 08:00 ?2次下載
    <b class='flag-5'>C</b>語言的拓展歸納總結<b class='flag-5'>詳細說明</b>

    5GSA的異常事件如何進行優化詳細說明

    5GSA的異常事件如何進行優化詳細說明
    發表于 12-11 00:48 ?15次下載
    5GSA的<b class='flag-5'>異常</b>事件如何進行優化<b class='flag-5'>詳細說明</b>

    C語言和C++的特點與用法詳細說明

    本文檔的主要內容詳細介紹的是C語言和C++的特點與用法詳細說明。
    的頭像 發表于 12-26 10:58 ?4454次閱讀

    Visual C++的介紹和數據類型詳細說明

    Visual C++的介紹和數據類型詳細說明介紹。
    發表于 03-29 15:32 ?17次下載

    C++程序異常處理機制是什么

    那么C++設計了一套異常處理機制,一方面能夠使得異常處理和正常運行代碼進行分離,使得程序更加模塊化;另一方面,C++
    的頭像 發表于 02-21 10:37 ?899次閱讀
    <b class='flag-5'>C++</b>程序<b class='flag-5'>異常</b>處理<b class='flag-5'>機制</b>是什么
    主站蜘蛛池模板: 最新果冻传媒在线观看免费版| 色偷偷超碰97人人澡人人| 久久天天躁狠狠躁夜夜呲| 欧美午夜福利主线路| 夜色福利院在线观看免费| 超碰caoporon最新视频| 久久久久久久久a免费| 色偷偷爱偷偷要| 99re热视频这里只有精品| 国产在线高清视频无码不卡| 女人18毛片| 在线国内自拍精品视频| 国产欧美一区二区精品性色tv | 国产日韩在线欧美视频| 免费视频精品38| 一二三四视频免费社区5| 国产成人拍精品免费视频爱情岛 | 攻把受做得合不拢腿play| 免费国产成人手机在线观看| 亚洲精品乱码电影在线观看| 国产AV在线传媒麻豆| 全部免费特黄特色大片看片| 97无码欧美熟妇人妻蜜| 久久精品国产亚洲AV久五月天| 亚瑟天堂久久一区二区影院| 大中国免费视频大全在线观看| 性女传奇快播| 丰满少妇67194视频| 欧美黄色第一页| 97视频免费观看| 免费毛片视频网站| 中国农村妇女真实BBWBBWBBW| 接吻吃胸摸下面啪啪教程| 午夜成a人片在线观看| 国产av免费观看日本| 日韩精品免费一区二区| www在线小视频免费| 日本高清加勒比| 高清国产激情视频在线观看| 日韩在线av免费视久久| 国产成人精品系列在线观看|