• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆 - 42  文章 - 3  trackbacks - 0
            <2009年7月>
            2829301234
            567891011
            12131415161718
            19202122232425
            2627282930311
            2345678

            常用鏈接

            留言簿(2)

            隨筆檔案

            文章檔案

            網(wǎng)頁(yè)收藏

            搜索

            •  

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            白楊

            http://baiy.cn

             

             

            在我?guī)啄昵伴_(kāi)始寫(xiě)《C++編碼規(guī)范與指導(dǎo)》一文時(shí),就已經(jīng)規(guī)劃著要加入這樣一篇討論 C++ 異常機(jī)制的文章了。沒(méi)想到時(shí)隔幾年以后才有機(jī)會(huì)把這個(gè)尾巴補(bǔ)完 :-)。

            還是那句開(kāi)場(chǎng)白:“在恰當(dāng)?shù)膱?chǎng)合使用恰當(dāng)?shù)奶匦?#8221; 對(duì)每個(gè)稱(chēng)職的 C++ 程序員來(lái)說(shuō)都是一個(gè)基本標(biāo)準(zhǔn)。想要做到這點(diǎn),就必須要了解語(yǔ)言中每個(gè)特性的實(shí)現(xiàn)方式及其時(shí)空開(kāi)銷(xiāo)。異常處理由于涉及大量底層內(nèi)容,向來(lái)是 C++ 各種高級(jí)機(jī)制中較難理解和透徹掌握的部分。本文將在盡量少引入底層細(xì)節(jié)的前提下,討論 C++ 中這一嶄新特性,并分析其實(shí)現(xiàn)開(kāi)銷(xiāo):

             

            關(guān)于線(xiàn)程

            進(jìn)程和線(xiàn)程的概念相信各位看官早已耳熟能詳。在這里,我只想帶大家回憶幾點(diǎn)重要概念:
            1. 一個(gè)進(jìn)程中可以同時(shí)包含多個(gè)線(xiàn)程。
               
            2. 我們通常認(rèn)為線(xiàn)程是操作系統(tǒng)可識(shí)別的最小并發(fā)執(zhí)行和調(diào)度單位(不要跟俺說(shuō)還有 Green Thread 或者 Fiber,OS Kernel 不認(rèn)識(shí)也不參與這些物件的調(diào)度)。
               
            3. 同一進(jìn)程中的多個(gè)線(xiàn)程共享代碼段(代碼和常量)、數(shù)據(jù)段(靜態(tài)和全局變量)和擴(kuò)展段(堆存儲(chǔ)),但是每個(gè)線(xiàn)程有自己的棧段。棧段又叫運(yùn)行時(shí)棧,用來(lái)存放所有局部變量和臨時(shí)變量(參數(shù)、返回值、臨時(shí)構(gòu)造的變量等)。這一條對(duì)下文中的某些概念來(lái)說(shuō)是非常重要的 。但是請(qǐng)注意,這里提到的各個(gè)“段”都是邏輯上的說(shuō)法,在物理上某些硬件架構(gòu)或者操作系統(tǒng)可能不使用段式存儲(chǔ)。不過(guò)沒(méi)關(guān)系,編譯器會(huì)保證這些邏輯概念和假設(shè)的前提條件對(duì)每個(gè) C/C++ 程序員來(lái)說(shuō)始終是成立的。
               
            4. 由于共享了除棧以外的所有內(nèi)存地址段,線(xiàn)程不可以有自己的“靜態(tài)”或“全局”變量,為了彌補(bǔ)這一缺憾,操作系統(tǒng)通常會(huì)提供一種稱(chēng)為 TLS(Thread Local Storage,即:“線(xiàn)程本地存儲(chǔ)”)的機(jī)制。通過(guò)該機(jī)制可以實(shí)現(xiàn)類(lèi)似的功能。TLS 通常是線(xiàn)程控制塊(TCB)中的某個(gè)指針?biāo)赶虻囊粋€(gè)指針數(shù)組,數(shù)組中的每個(gè)元素稱(chēng)為一個(gè)槽(Slot),每個(gè)槽中的指針由使用者定義,可以指向任意位置(但通常是指向堆存儲(chǔ)中的某個(gè)偏移)。

             

            函數(shù)的調(diào)用和返回

            接著我們來(lái)回顧下一個(gè)預(yù)備知識(shí):編譯器如何實(shí)現(xiàn)函數(shù)的調(diào)用和返回。一般來(lái)說(shuō),編譯器會(huì)為當(dāng)前調(diào)用棧里的每個(gè)函數(shù)建立一個(gè)棧框架(Stack Frame)。“棧框架”擔(dān)負(fù)著以下重要任務(wù):
            1. 傳遞參數(shù):通常,函數(shù)的調(diào)用參數(shù)總是在這個(gè)函數(shù)棧框架的最頂端。
            2. 傳遞返回地址:告訴被調(diào)用者的 return 語(yǔ)句應(yīng)該 return 到哪里去,通常指向該函數(shù)調(diào)用的下一條語(yǔ)句(代碼段中的偏移)。
            3. 存放調(diào)用者的當(dāng)前棧指針:便于清理被調(diào)用者的所有局部變量、并恢復(fù)調(diào)用者的現(xiàn)場(chǎng)。
            4. 存放當(dāng)前函數(shù)內(nèi)的所有局部變量:記得嗎?剛才說(shuō)過(guò)所有局部和臨時(shí)變量都是存儲(chǔ)在棧上的。

            最后再?gòu)?fù)習(xí)一點(diǎn):棧是一種“后進(jìn)先出”(LIFO)的數(shù)據(jù)結(jié)構(gòu),不過(guò)實(shí)際上大部分棧的實(shí)現(xiàn)都支持隨機(jī)訪(fǎng)問(wèn)。

            下面我們來(lái)看個(gè)具體例子:

            假設(shè)有 FuncA、FuncB 和 FuncC 三個(gè)函數(shù),每個(gè)函數(shù)均接收兩個(gè)整形值作為其參數(shù)。在某線(xiàn)程上的某一時(shí)間段內(nèi),F(xiàn)uncA 調(diào)用了 FuncB,而 FuncB 又調(diào)用了 FuncC。則,它們的棧框架看起來(lái)應(yīng)該像這樣:


            圖1 函數(shù)調(diào)用棧框架示例

            正如上圖所示的那樣,隨著函數(shù)被逐級(jí)調(diào)用,編譯器會(huì)為每一個(gè)函數(shù)建立自己的棧框架,棧空間逐漸消耗。隨著函數(shù)的逐級(jí)返回,該函數(shù)的棧框架也將被逐級(jí)銷(xiāo)毀,棧空間得以逐步釋放。順便說(shuō)一句,遞歸函數(shù)的嵌套調(diào)用深度通常也是取決于運(yùn)行時(shí)棧空間的剩余尺寸。

            這里順便解釋另一個(gè)術(shù)語(yǔ):調(diào)用約定(calling convention)。調(diào)用約定通常指:調(diào)用者將參數(shù)壓入棧中(或放入寄存器中)的順序,以及返回時(shí)由誰(shuí)(調(diào)用者還是被調(diào)用者)來(lái)清理這些參數(shù)等細(xì)節(jié)規(guī)程方面的約定。

            最后再說(shuō)一句,這里所展示的函數(shù)調(diào)用乃是最“經(jīng)典”的方式。實(shí)際情況是:在開(kāi)啟了優(yōu)化選項(xiàng)后,編譯器可能不會(huì)為一個(gè)內(nèi)聯(lián)甚至非內(nèi)聯(lián)的函數(shù)生成棧框架,編譯器可能使用很多優(yōu)化技術(shù)消除這個(gè)構(gòu)造。不過(guò)對(duì)于一個(gè) C/C++ 程序員來(lái)說(shuō),達(dá)到這樣的理解程度通常就足夠了。


             

            C++ 函數(shù)的調(diào)用和返回

            首先澄清一點(diǎn),這里說(shuō)的 “C++ 函數(shù)”是指:
            1. 該函數(shù)可能會(huì)直接或間接地拋出一個(gè)異常:即該函數(shù)的定義存放在一個(gè) C++ 編譯(而不是傳統(tǒng) C)單元內(nèi),并且該函數(shù)沒(méi)有使用“throw()”異常過(guò)濾器。
            2. 或者該函數(shù)的定義內(nèi)使用了 try 塊。

            以上兩者滿(mǎn)足其一即可。為了能夠成功地捕獲異常和正確地完成棧回退(stack unwind),編譯器必須要引入一些額外的數(shù)據(jù)結(jié)構(gòu)和相應(yīng)的處理機(jī)制。我們首先來(lái)看看引入了異常處理機(jī)制的棧框架大概是什么樣子:


            圖2 C++函數(shù)調(diào)用棧框架示例

            由圖2可見(jiàn),在每個(gè) C++ 函數(shù)的棧框架中都多了一些東西。仔細(xì)觀察的話(huà),你會(huì)發(fā)現(xiàn),多出來(lái)的東西正好是一個(gè) EXP 類(lèi)型的結(jié)構(gòu)體。進(jìn)一步分析就會(huì)發(fā)現(xiàn),這是一個(gè)典型的單向鏈表式結(jié)構(gòu):

            • piPrev 成員指向鏈表的上一個(gè)節(jié)點(diǎn),它主要用于在函數(shù)調(diào)用棧中逐級(jí)向上尋找匹配的 catch 塊,并完成棧回退工作。

            • piHandler 成員指向完成異常捕獲和棧回退所必須的數(shù)據(jù)結(jié)構(gòu)(主要是兩張記載著關(guān)鍵數(shù)據(jù)的表:“try”塊表:tblTryBlocks 及“棧回退表”:tblUnwind)。

            • nStep 成員用來(lái)定位 try 塊,以及在棧回退表中尋找正確的入口。

            需要說(shuō)明的是:編譯器會(huì)為每一個(gè)“C++ 函數(shù)”定義一個(gè) EHDL 結(jié)構(gòu),不過(guò)只會(huì)為包含了“try”塊的函數(shù)定義 tblTryBlocks 成員。此外,異常處理器還會(huì)為每個(gè)線(xiàn)程維護(hù)一個(gè)指向當(dāng)前異常處理框架的指針。該指針指向異常處理器鏈表的鏈尾,通常存放在某個(gè) TLS 槽或能起到類(lèi)似作用的地方。

            最后,請(qǐng)?jiān)倏匆槐閳D2,并至少對(duì)其中的數(shù)據(jù)結(jié)構(gòu)留下一個(gè)大體印象。我們會(huì)在后面多個(gè)小節(jié)中詳細(xì)討論它們。

            注意:為了簡(jiǎn)化起見(jiàn),本文中描述的數(shù)據(jù)結(jié)構(gòu)內(nèi),大多省略了一些與話(huà)題無(wú)關(guān)的成員。

             

            棧回退(Stack Unwind)機(jī)制

            “棧回退”是伴隨異常處理機(jī)制引入 C++ 中的一個(gè)新概念,主要用來(lái)確保在異常被拋出、捕獲并處理后,所有生命期已結(jié)束的對(duì)象都會(huì)被正確地析構(gòu),它們所占用的空間會(huì)被正確地回收。

            受益于棧回退機(jī)制的引入,以及 C++ 類(lèi)所支持的“資源申請(qǐng)即初始化”語(yǔ)意,使得我們終于能夠徹底告別既不優(yōu)雅也不安全的 setjmp/longjmp 調(diào)用,簡(jiǎn)便又安全地實(shí)現(xiàn)遠(yuǎn)程跳轉(zhuǎn)了。我想這也是 C++ 異常處理機(jī)制在錯(cuò)誤處理以外唯一一種合理的應(yīng)用方式了。

            下面我們就來(lái)具體看看編譯器是如何實(shí)現(xiàn)棧回退機(jī)制的:


            圖3 C++ 棧回退機(jī)制

            圖3中的“FuncUnWind”函數(shù)內(nèi),所有真實(shí)代碼均以黑色和藍(lán)色字體標(biāo)示,編譯器生成的代碼則由灰色和橙色字體標(biāo)明。此時(shí),在圖2里給出的 nStep 變量和 tblUnwind 成員作用就十分明顯了。

            nStep 變量用于跟蹤函數(shù)內(nèi)局部對(duì)象的構(gòu)造、析構(gòu)階段。再配合編譯器為每個(gè)函數(shù)生成的 tblUnwind 表,就可以完成退棧機(jī)制。表中的 pfnDestroyer 字段記錄了對(duì)應(yīng)階段應(yīng)當(dāng)執(zhí)行的析構(gòu)操作(析構(gòu)函數(shù)指針);pObj 字段則記錄了與之相對(duì)應(yīng)的對(duì)象 this 指針偏移。將 pObj 所指的偏移值加上當(dāng)前棧框架基址(EBP),就是要代入 pfnDestroyer 所指析構(gòu)函數(shù)的 this 指針,這樣即可完成對(duì)該對(duì)象的析構(gòu)工作。而 nNextIdx 字段則指向下一個(gè)需要析構(gòu)對(duì)象所在的行(下標(biāo))。

            在發(fā)生異常時(shí),異常處理器首先檢查當(dāng)前函數(shù)棧框架內(nèi)的 nStep 值,并通過(guò) piHandler 取得 tblUnwind[] 表。然后將 nStep 作為下標(biāo)帶入表中,執(zhí)行該行定義的析構(gòu)操作,然后轉(zhuǎn)向由 nNextIdx 指向的下一行,直到 nNextIdx 為 -1 為止。在當(dāng)前函數(shù)的棧回退工作結(jié)束后,異常處理器可沿當(dāng)前函數(shù)棧框架內(nèi) piPrev 的值回溯到異常處理鏈中的上一節(jié)點(diǎn)重復(fù)上述操作,直到所有回退工作完成為止。

            值得一提的是,nStep 的值完全在編譯時(shí)決定,運(yùn)行時(shí)僅需執(zhí)行若干次簡(jiǎn)單的整形立即數(shù)賦值(通常是直接賦值給CPU里的某個(gè)寄存器)。此外,對(duì)于所有內(nèi)部類(lèi)型以及使用了默認(rèn)構(gòu)造、析構(gòu)方法(并且它的所有成員和基類(lèi)也使用了默認(rèn)方法)的類(lèi)型,其創(chuàng)建和銷(xiāo)毀均不影響 nStep 的值。

            注意:如果在棧回退的過(guò)程中,由于析構(gòu)函數(shù)的調(diào)用而再次引發(fā)了異常(異常中的異常),則被認(rèn)為是一次異常處理機(jī)制的嚴(yán)重失敗。此時(shí)進(jìn)程將被強(qiáng)行禁止。為防止出現(xiàn)這種情況,應(yīng)在所有可能拋出異常的析構(gòu)函數(shù)中使用“std::uncaught_exception()”方法判斷當(dāng)前是否正在進(jìn)行棧回退(即:存在一個(gè)未捕獲或未完全處理完畢的異常)。如是,則應(yīng)抑制異常的再次拋出。

             

            異常捕獲機(jī)制

            一個(gè)異常被拋出時(shí),就會(huì)立即引發(fā) C++ 的異常捕獲機(jī)制:


            圖4 C++ 異常捕獲機(jī)制

            在上一小節(jié)中,我們已經(jīng)看到了 nStep 變量在跟蹤對(duì)象構(gòu)造、析構(gòu)方面的作用。實(shí)際上 nStep 除了能夠跟蹤對(duì)象創(chuàng)建、銷(xiāo)毀階段以外,還能夠標(biāo)識(shí)當(dāng)前執(zhí)行點(diǎn)是否在 try 塊中,以及(如果當(dāng)前函數(shù)有多個(gè) try 塊的話(huà))究竟在哪個(gè) try 塊中。這是通過(guò)在每一個(gè) try 塊的入口和出口各為 nStep 賦予一個(gè)唯一 ID 值,并確保 nStep 在對(duì)應(yīng) try 塊內(nèi)的變化恰在此范圍之內(nèi)來(lái)實(shí)現(xiàn)的。

            在具體實(shí)現(xiàn)異常捕獲時(shí),首先,C++ 異常處理器檢查發(fā)生異常的位置是否在當(dāng)前函數(shù)的某個(gè) try 塊之內(nèi)。這項(xiàng)工作可以通過(guò)將當(dāng)前函數(shù)的 nStep 值依次在 piHandler 指向 tblTryBlocks[] 表的條目中進(jìn)行范圍為 [nBeginStep, nEndStep) 的比對(duì)來(lái)完成。

            例如:若圖4 中的 FuncB 在 nStep == 2 時(shí)發(fā)生了異常,則通過(guò)比對(duì) FuncB 的 tblTryBlocks[] 表發(fā)現(xiàn) 2∈[1, 3),故該異常發(fā)生在 FuncB 內(nèi)的第一個(gè) try 塊中。

            其次,如果異常發(fā)生的位置在當(dāng)前函數(shù)中的某個(gè) try 塊內(nèi),則嘗試匹配該 tblTryBlocks[] 相應(yīng)條目中的 tblCatchBlocks[] 表。tblCatchBlocks[] 表中記錄了與指定 try 塊配套出現(xiàn)的所有 catch 塊相關(guān)信息,包括這個(gè) catch 塊所能捕獲的異常類(lèi)型及其起始地址等信息。

            若找到了一個(gè)匹配的 catch 塊,則復(fù)制當(dāng)前異常對(duì)象到此 catch 塊,然后跳轉(zhuǎn)到其入口地址執(zhí)行塊內(nèi)代碼。

            否則,則說(shuō)明異常發(fā)生位置不在當(dāng)前函數(shù)的 try 塊內(nèi),或者這個(gè) try 塊中沒(méi)有與當(dāng)前異常相匹配的 catch 塊,此時(shí)則沿著函數(shù)棧框架中 piPrev 所指地址(即:異常處理鏈中的上一個(gè)節(jié)點(diǎn))逐級(jí)重復(fù)以上過(guò)程,直至找到一個(gè)匹配的 catch 塊或到達(dá)異常處理鏈的首節(jié)點(diǎn)。對(duì)于后者,我們稱(chēng)為發(fā)生了未捕獲的異常,對(duì)于 C++ 異常處理器而言,未捕獲的異常是一個(gè)嚴(yán)重錯(cuò)誤,將導(dǎo)致當(dāng)前進(jìn)程被強(qiáng)制結(jié)束。

            注意:雖然在圖4示例中的 tblTryBlocks[] 只有一個(gè)條目,這個(gè)條目中的 tblCatchBlocks[] 也只有一行。但是在實(shí)際情況中,這兩個(gè)表中都允許有多條記錄。意即:一個(gè)函數(shù)中可以有多個(gè) try 塊,每個(gè) try 塊后均可跟隨多個(gè)與之配套的 catch 塊。

            注意:按照標(biāo)準(zhǔn)意義上的理解,異常時(shí)的棧回退是伴隨著異常捕獲過(guò)程沿著異常處理鏈逐層向上進(jìn)行的。但是有些編譯器是在先完成異常捕獲后再一次性進(jìn)行棧回退的。無(wú)論具體實(shí)現(xiàn)使用了哪種方式,除非正在開(kāi)發(fā)一個(gè)內(nèi)存嚴(yán)格受限的嵌入式應(yīng)用,通常我們按照標(biāo)準(zhǔn)語(yǔ)意來(lái)理解都不會(huì)產(chǎn)生什么問(wèn)題。

            備注:實(shí)際上 tblCatchBlocks 中還有一些較為關(guān)鍵但被故意省略的字段。比如指明該 catch 塊異常對(duì)象復(fù)制方式(傳值(拷貝構(gòu)造)或傳址(引用或指針))的字段,以及在何處存放被復(fù)制的異常對(duì)象(相對(duì)于入口地址的偏移位置)等信息。

             
             

            異常的拋出

            接下來(lái)討論整個(gè) C++ 異常處理機(jī)制中的最后一個(gè)環(huán)節(jié),異常的拋出:


            圖5 C++ 異常拋出

            在編譯一段 C++ 代碼時(shí),編譯器會(huì)將所有 throw 語(yǔ)句替換為其 C++ 運(yùn)行時(shí)庫(kù)中的某一指定函數(shù),這里我們叫它 __CxxRTThrowExp(與本文提到的所有其它數(shù)據(jù)結(jié)構(gòu)和屬性名一樣,在實(shí)際應(yīng)用中它可以是任意名稱(chēng))。該函數(shù)接收一個(gè)編譯器認(rèn)可的內(nèi)部結(jié)構(gòu)(我們叫它 EXCEPTION 結(jié)構(gòu))。這個(gè)結(jié)構(gòu)中包含了待拋出異常對(duì)象的起始地址、用于銷(xiāo)毀它的析構(gòu)函數(shù),以及它的 type_info 信息。對(duì)于沒(méi)有啟用 RTTI 機(jī)制(編譯器禁用了 RTTI 機(jī)制或沒(méi)有在類(lèi)層次結(jié)構(gòu)中使用虛表)的異常類(lèi)層次結(jié)構(gòu),可能還要包含其所有基類(lèi)的 type_info 信息,以便與相應(yīng)的 catch 塊進(jìn)行匹配。

            在圖5中的深灰色框圖內(nèi),我們使用 C++ 偽代碼展示了函數(shù) FuncA 中的 “throw myExp(1);” 語(yǔ)句將被編譯器最終翻譯成的樣子。實(shí)際上在多數(shù)情況下,__CxxRTThrowExp 函數(shù)即我們前面曾多次提到的“異常處理器”,異常捕獲和棧回退等各項(xiàng)重要工作都由它來(lái)完成。

            __CxxRTThrowExp 首先接收(并保存)EXCEPTION 對(duì)象;然后從 TLS:Current ExpHdl 處找到與當(dāng)前函數(shù)對(duì)應(yīng)的 piHandler、nStep 等異常處理相關(guān)數(shù)據(jù);并按照前文所述的機(jī)制完成異常捕獲和棧回退。由此完成了包括“拋出”->“捕獲”->“回退”等步驟的整套異常處理機(jī)制。

             

            Windows 中的結(jié)構(gòu)化異常處理

            Microsoft Windows 帶有一種名為“結(jié)構(gòu)化異常處理”的機(jī)制,非常著名的“內(nèi)存訪(fǎng)問(wèn)違例”出錯(cuò)對(duì)話(huà)框就是該機(jī)制的一種體現(xiàn)。Windows 結(jié)構(gòu)化異常處理與前文討論的 C++ 異常處理機(jī)制有驚人的相似之處,同樣使用類(lèi)似的鏈?zhǔn)浇Y(jié)構(gòu)實(shí)現(xiàn)。對(duì)于 Windows 下的應(yīng)用程序,只需使用 SetUnhandledExceptionFilter API 注冊(cè)異常處理器;用 FS:[0] 替代前文所述的 TLS: Current ExpHdl 等很少的改動(dòng),即可將此兩種錯(cuò)誤處理機(jī)制合而為一。這樣做的優(yōu)勢(shì)十分明顯:
            • 由于可直接借助操作系統(tǒng)提供的機(jī)制,所以簡(jiǎn)化了 C++ 異常處理器的實(shí)現(xiàn)。
            • 使“catch (...)” 塊得以捕獲操作系統(tǒng)產(chǎn)生的異常(如:“內(nèi)存訪(fǎng)問(wèn)違例”等等)。
            • 使操作系統(tǒng)的異常處理機(jī)制能夠捕獲所有 C++ 異常。

            實(shí)際上,大多數(shù) Windows 下的 C++ 編譯器的異常機(jī)制均使用這種方式實(shí)現(xiàn)。

             
             

            異常處理機(jī)制的開(kāi)銷(xiāo)分析

            至此,我們已完整地闡述了整套 C++ 異常處理機(jī)制的實(shí)現(xiàn)原理。我在本文的開(kāi)頭曾提到,作為一名 C++ 程序員,了解其某一特性的實(shí)現(xiàn)原理主要是為了避免錯(cuò)誤地使用該特性。要達(dá)到這個(gè)目的,還要在了解實(shí)現(xiàn)原理的基礎(chǔ)上進(jìn)行一些額外的開(kāi)銷(xiāo)分析工作:
             
            特性 時(shí)間開(kāi)銷(xiāo) 空間開(kāi)銷(xiāo)
            EHDL 無(wú)運(yùn)行時(shí)開(kāi)銷(xiāo) 每“C++函數(shù)”一個(gè) EHDL 對(duì)象,其中的 tblTryBlocks[] 成員僅在函數(shù)中包含至少一個(gè) try 塊時(shí)使用。典型情況下小于 64 字節(jié)。

             

            C++棧框架 極高的 O(1) 效率,每次調(diào)用時(shí)進(jìn)行3次額外的整形賦值和一次 TLS 訪(fǎng)問(wèn)。 每 調(diào)用兩個(gè)指針和一個(gè)整形開(kāi)銷(xiāo)。典型情況下小于 16 字節(jié)。

             

            step 跟蹤 極高的 O(1) 效率每次進(jìn)出 try 塊或?qū)ο髽?gòu)造/析構(gòu)一次整形立即數(shù)賦值。 無(wú)(已記入 C++ 棧框架中的相應(yīng)項(xiàng)目)。

             

            異常的拋出、捕獲和棧回退 異常的拋出是一次 O(1) 級(jí)操作。在單個(gè)函數(shù)中進(jìn)行捕獲和棧回退也均為 O(1) 操作。

            但異常捕獲的總體成本為 O(m),其中 m 等于當(dāng)前函數(shù)調(diào)用棧中,從拋出異常的位置到達(dá)匹配 catch 塊之間所經(jīng)過(guò)的函數(shù)調(diào)用中,包含 try 塊(即:定義了有效 tblTryBlocks[])的函數(shù)個(gè)數(shù)。

            棧回退的總成本為 O(n),其中 n 等于當(dāng)前函數(shù)調(diào)用棧中,從拋出異常的位置到達(dá)匹配 catch 塊之間所經(jīng)過(guò)的函數(shù)調(diào)用數(shù)。

            在異常處理結(jié)束前,需保存異常對(duì)象及其析構(gòu)函數(shù)指針和相應(yīng)的 type_info 信息。

            具體根據(jù)對(duì)象尺寸、編譯器選項(xiàng)(是否開(kāi)啟 RTTI)及異常捕獲器的參數(shù)傳遞方式(傳值或傳址)等因素有較大變化。典型情況下小于 256 字節(jié)。

             

            可以看出,在沒(méi)有拋出異常時(shí),C++ 的異常處理機(jī)制是十分有效的。在有異常被拋出后,可能會(huì)依當(dāng)前函數(shù)調(diào)用棧的情形進(jìn)行若干次整形比較(try塊表匹配)操作,但這通常不會(huì)超過(guò)幾十次。對(duì)于大多數(shù) 15 年前的 CPU 來(lái)說(shuō),整形比較也只需 1 時(shí)鐘周期,所以異常捕獲的效率還是很高的。棧回退的效率則與 return 語(yǔ)句基本相當(dāng)。

            考慮到即使是傳統(tǒng)的函數(shù)調(diào)用、錯(cuò)誤處理和逐級(jí)返回機(jī)制也不是沒(méi)有代價(jià)的。這些開(kāi)銷(xiāo)在絕大多數(shù)情形下仍可以接受。空間開(kāi)銷(xiāo)方面,每“C++ 函數(shù)”一個(gè) EHDL 結(jié)構(gòu)體的引入在某些極端情形下會(huì)明顯增加目標(biāo)文件尺寸和內(nèi)存開(kāi)銷(xiāo)。但是典型情況下,它們的影響并不大,但也沒(méi)有小到可以完全忽略的程度。如果正在為一個(gè)資源嚴(yán)格受限的環(huán)境開(kāi)發(fā)應(yīng)用程序,你可能需要考慮關(guān)閉異常處理和 RTTI 機(jī)制以節(jié)約存儲(chǔ)空間。

            以上討論的是一種典型的異常機(jī)制的實(shí)現(xiàn)方式,各具體編譯器廠(chǎng)商可能有自己的優(yōu)化和改進(jìn)方案,但總體的出入不會(huì)很大。

             

            小節(jié)

            異常處理是 C++ 中十分有用的嶄新特性之一。在絕大多數(shù)情況下,它們都有著優(yōu)異的表現(xiàn)和令人滿(mǎn)意的時(shí)空效率。異常處理本質(zhì)上是另一種返回機(jī)制。但無(wú)論從軟件工程、模塊設(shè)計(jì)、編碼習(xí)慣還是時(shí)空效率等角度來(lái)說(shuō),除了在有充分文檔說(shuō)明的前提下,偶爾可用來(lái)替代替代傳統(tǒng)的 setjmp/longjmp 功能外,應(yīng)保證只將其用于程序的錯(cuò)誤處理機(jī)制中。

            此外,由于長(zhǎng)跳轉(zhuǎn)的使用既易于出錯(cuò),又難于理解和維護(hù)。在編碼過(guò)程中也應(yīng)當(dāng)盡量避免使用。關(guān)于異常的一般性使用說(shuō)明,請(qǐng)參考:代碼風(fēng)格與版式:異常

            posted on 2011-12-13 18:23 鷹擊長(zhǎng)空 閱讀(262) 評(píng)論(0)  編輯 收藏 引用

            只有注冊(cè)用戶(hù)登錄后才能發(fā)表評(píng)論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問(wèn)   Chat2DB   管理


            国内精品久久久久影院薰衣草 | 久久久久久久综合日本| 99久久精品免费看国产免费| 国产91久久综合| 久久亚洲中文字幕精品一区| 伊人久久综合成人网| 久久久久久亚洲Av无码精品专口 | 97久久精品人妻人人搡人人玩| 久久最近最新中文字幕大全| 久久se精品一区精品二区国产| 久久久久久国产精品无码下载 | 久久人人爽人人爽人人片AV东京热| 色综合久久久久综合99| 久久精品黄AA片一区二区三区| 精品多毛少妇人妻AV免费久久| 噜噜噜色噜噜噜久久| 久久久久久久尹人综合网亚洲| 中文精品99久久国产 | 久久久一本精品99久久精品88| 国产精品欧美久久久久无广告| 亚洲国产精品一区二区久久hs| 久久精品无码一区二区三区日韩| 久久精品中文字幕无码绿巨人| 久久大香萑太香蕉av| 国产视频久久| 日韩亚洲欧美久久久www综合网| 久久SE精品一区二区| 国产精品久久久久久久久软件| 国产精品久久久久久久久久免费| 久久国产精品99久久久久久老狼 | 99久久99久久精品国产片果冻| 久久国产视频网| 久久激情亚洲精品无码?V| 国产精品久久久久久一区二区三区| 亚洲国产精品成人久久| 狠狠色丁香久久婷婷综合蜜芽五月| 久久人人爽人人澡人人高潮AV | 蜜臀av性久久久久蜜臀aⅴ麻豆| 亚洲国产日韩欧美综合久久| 久久免费99精品国产自在现线| 久久久久久亚洲精品不卡 |