當前位置:首頁 > 嵌入式 > 嵌入式教程
[導讀]異常機制簡單探討

引 言
    我們在編寫軟件時不但要追求代碼的正確性,更要關注程序的容錯能力,在環(huán)境不正確或操作不當時不能死機,更不能造成災難性后果。程序運行時有些錯誤是不可避免的,如內存不足、文件打開失敗、數(shù)組下標溢出等,這時要力爭做到排除錯誤,繼續(xù)運行。
    傳統(tǒng)做法是返回一個錯誤代碼,調用者通過if等語句測試返回值來判斷是否成功。這樣做有幾個缺點:首先,增加的條件語句可能會帶來更多的錯誤;其次,條件語句是分支點,會增加測試難度;另外,構造函數(shù)沒有返回值,返回錯誤代碼是不可能的。
    C++的異常機制為我們提供了更好的解決方法。異常處理的基本思想是:當出現(xiàn)錯誤時拋出一個異常,希望它的調用者能捕獲并處理這個異常。如果調用者也不能處理這個異常,那么異常會傳遞給上級調用,直到被捕獲處理為止。如果程序始終沒有處理這個異常,最終它會被傳到C++運行環(huán)境,運行環(huán)境捕獲后通常只是簡單地終止這個程序。異常機制使得正常代碼和錯誤處理代碼清晰地劃分開來,程序變得非常干凈并且容易維護。
    但是如何合理地使用異常機制來達到預期的效果呢?MISRA C++給出了一些推薦的規(guī)則,幫助程序員更加合理、可靠地實現(xiàn)異常機制。下面將結合這些規(guī)則對異常機制進行簡單的探討。
 

1 在恰當?shù)膱龊鲜褂们‘數(shù)奶匦?/b>
    MISRA C++對異常的第1條規(guī)則就是:
    規(guī)則15-0-1(不容討論):異常機制只能用來處理錯誤。
    異常處理的本質是控制流程的轉移,但異常機制是針對錯誤處理的,僅在代碼可能出現(xiàn)異常的情況下使用,不能用來實現(xiàn)普通的流程轉移。
    例如:

    
    語法不會阻止你這樣做,但殺雞焉用牛刀。這樣不但會降低程序的可讀性,也會帶來更大的開銷。實際上,用一個簡單的if語句就可以實現(xiàn)上述邏輯。同樣,出于程序流程的清晰性考慮的還有:
    規(guī)則15-0-3(強制):不允許通過goto或者switch語句跳轉到try或catch語句塊內。
 

2 正確地拋出異常
   
什么時候,什么地方,拋出什么樣的異常,都是需要仔細考慮的。MISRA C++對此也作了相關規(guī)定。首先,來看一下拋出異常對象的類型中有哪些需要注意的地方。規(guī)則15-0-2(推薦):拋出的異常對象不應該是指針類型。
    如果拋出的異常對象是個指針類型,指向的是動態(tài)創(chuàng)建的對象,那么這個對象應該由哪個函數(shù)來負責銷毀,什么時候銷毀,都很不清楚。比如說,如果是在堆中建立的對象,那通常必須刪除,否則會造成資源泄漏;如果不是在堆中建立的對象,通常不能刪除,否則程序的行為將不可預測。
    規(guī)則15-1-2(強制):不能顯式地把NULL作為異常對象拋出。
    因為throw(NULL)=tbrow(0),因此NULL會被當作整型捕獲,而不是空指針常量,這可能與程序員的預期不一致。
    通常,很多函數(shù)都是基于function-try-block結構的,即函數(shù)體整個包含在一個函數(shù)try塊中。而函數(shù)能拋出什么類型的異常對象,有以下規(guī)定:
    規(guī)則15-5-2(強制):如果一個函數(shù)聲明時指定了具體的異常類型,那么它只能拋出指定類型的異常。
    規(guī)則15-4-1(強制):如果一個函數(shù)聲明時指定了異常的類型,那么在其他編譯單元里該函數(shù)的聲明必須有同樣的指定。
    函數(shù)的代碼結構如下:返回值類型函數(shù)名(形參表)throw(類型名表){函數(shù)體}
    如果函數(shù)在聲明時沒有異常規(guī)范,那么它可以拋出任意類型的異常對象;如果異常類型為空,則表示不拋出任何類型異常。注意這兩者之間的區(qū)別,前者指沒有throw(類型名表)語句,而后者有throw(類型名表),只是類型名表為空。但如果聲明時指定了異常的類型,那么它只能拋出指定類型的異常。
    另外,函數(shù)原型中的異常聲明要與實現(xiàn)中的異常聲明一致,否則會引起異常沖突。由于異常機制是在運行出現(xiàn)異常時才發(fā)揮作用的,因此如果函數(shù)的實現(xiàn)中拋出了沒有在其異常聲明列表中列出的異常,編譯器也許不能檢查出來。當拋出一個未在其異常聲明列表里的異常類型時,unexpected()函數(shù)會被調用,默認會導致std::bad_exception類型的異常被拋出。如果std::bad_exception不在異常聲明列表里,又會導致terminate()被調用,從而導致程序結束。
    對于什么時候能拋出異常,則有以下規(guī)定:
    規(guī)則15-3-1(強制):異常只能在初始化之后而且程序結束之前拋出。
    在執(zhí)行main函數(shù)體之前,是初始化階段,構造和初始化靜態(tài)對象;在main函數(shù)返回后,是終止階段,靜態(tài)對象被銷毀。在這兩個階段中如果拋出異常,會導致程序以不定的方式終止(這依賴于具體的編譯器)。例如:
   

    在這個例子中,catch塊只能捕獲上面try塊中的異常。如果在對象c的構造函數(shù)或析構函數(shù)中拋出異常,并不能被main里的catch塊捕獲,而且會導致程序終止。
    除了上述規(guī)則,還有以下兩個規(guī)則需要注意:
    規(guī)則15-1-1(強制):throw語句中的表達式本身不能引發(fā)新的異常。
    如果在構造異常對象,或者計算賦值表達式時引發(fā)新的異常,那么新的異常會在本來要拋出的異常之前被拋出,這與程序員的預期不一致。
    規(guī)則15-1-3(強制):空的throw語句只能出現(xiàn)在catch語句塊中。
    空的throw用來將捕獲的異常再拋出,可以實現(xiàn)多個處理程序問異常的傳遞。然而,如果在catch語句外用,由于沒有捕獲到異常,也就沒有東西可以再拋出,這樣會導致程序以不定的方式終止(這依賴具體的編譯器)。
 

3 合理地處理異常
   
由于后面的討論多處涉及到“棧展開”這個概念,這里先解釋一下。“棧展開”是異常機制中一個重要的過程:在逐層查找用來處理異常的catch子句時,因為異常而退出復合語句和函數(shù)定義,這個過程被稱作“棧展開”。隨著棧的展開,在退出的復合語句和函數(shù)定義中聲明的局部變量的生命期也結束,而且這些局部類對象的析構函數(shù)也會被調用,這樣能保證內存空間得到合理回收。棧展開的概念對于理解后面的內容很重要,我們通過一個具體例子進一步闡述。

    
    當異常發(fā)生時,在函數(shù)調用鏈中逐層查找該異常的catch子句。在棧展開過程中函數(shù)foo()首先被檢查到,因為產(chǎn)生異常的語句沒有被放在try塊中,所以不會在:foo()中查找針對該異常的catch子句。棧展開過程繼續(xù)向上遍歷函數(shù)調用鏈到達調用foo()的函數(shù)。然而在foo()帶著這個未處理的異常退出之前,棧展開過程會銷毀foo()中所有在異常產(chǎn)生之前被創(chuàng)建的局部類對象。結果就是:o1、o2的析構函數(shù)被調用,o3已經(jīng)“死亡”,而o4還沒“出生”。
    顧名思義,“異常”就是程序運行出現(xiàn)了非預期的情況,或者說錯誤。因此,出現(xiàn)異常必須有針對地處理。對此,MISRA C++首先有如下規(guī)定:
    規(guī)則15-3-4(強制):所有可能的流程中顯式拋出來的異常都應該有一個類型兼容的處理程序。
    規(guī)則15-3-2(推薦):至少要有一個處理程序來處理所有其他未針對處理的異常。
    如果程序拋出一個沒有被處理的異常,程序會終止,而終止前調用棧有沒有被“展開”,動態(tài)對象能不能被析構,這些都依賴于編譯器。上面兩條規(guī)則規(guī)定了:不但預期拋出的異常要進行處理,其他可能被拋出的異常也要有相應的處理措施。請注意規(guī)則15-3-4中“類型兼容”的字眼,C++有非常靈活的類型兼容規(guī)則,尤其對于類。例如當異常對象是派生類時,“兼容類型”可以是派生類,也可以是基類。后面我們還會具體討論這個問題。
    一個try塊后可以有多個catch塊來捕獲不同的異常。當出現(xiàn)異常時,catch處理程序按照其在try塊后出現(xiàn)的順序被逐個檢查,只要找到一個匹配的異常類型,后面的異常處理都被忽略。因此,catch處理程序出現(xiàn)的順序很重要。
    規(guī)則15-3-6(強制):若一個try-catch語句塊有多個處理程序,或者一個派生類和其部分或全部基類的function-try-block塊有多個處理程序,處理程序的順序應該是先派生類后基類。
    規(guī)則15-3-7(強制):若一個try-catch語句塊或者function-try-block塊有多個處理程序時,catch(…)處理程序(捕獲所有異常)應該放在最后。
    這是因為根據(jù)類型兼容規(guī)則,異常對象為派生類時可以被針對基類的處理程序所捕獲。如果針對基類的處理程序放在前面,后面針對派生類的處理程序就不會被執(zhí)行到。同理,catch(…)處理程序能捕獲所有類型的異常,在其后面所有的異常處理程序都不會被執(zhí)行到。[!--empirenews.page--]根據(jù)上述規(guī)則,典型的try-catch的結構示例如下:

 

    

    細心的讀者也許會發(fā)現(xiàn),上面例子中是通過引用來捕獲類的對象。當異常對象類型為某個類時,有3種方式傳遞到catch子句里:指針、傳值和引用。也許大家首先想到的是指針,指針的確是效率很高的工具,而且不涉及到對象拷貝。但不要忘了,前面的規(guī)則15-0-2中明確指出,拋出的異常對象不應該是指針類型。而對于傳值和引用,在MISRA C++中給出的規(guī)定是:通過引用捕獲異常。
    規(guī)則15-3-5(強制):若異常對象為類的對象時,應該通過引用來捕獲。
    通過值傳遞,不但會增加拷貝對象的開銷,而且還會出現(xiàn)“退化”問題。所謂“退化”是指:如果異常對象是一個派生類對象,但被作為基類捕獲,那么只有基類的函數(shù)(包括虛函數(shù))能被調用,派生類中增加的數(shù)據(jù)成員都不能被訪問。通過引用捕獲則沒有這個問題。下面的例子具體地說明了“退化”問題:

    
    鑒于類的構造函數(shù)和析構函數(shù)的特殊性,還有兩點需要注意。
    規(guī)則15-3-3(強制):如果類的構造函數(shù)和析構函數(shù)是function-try-block結構的,在catch處理程序中不能引用該類或其基類的非靜態(tài)成員。
    這種行為的后果是不定的。比如說,當構造對象分配內存時拋出了異常,這時該對象本身還不存在,訪問其成員也就出錯。相反,在析構函數(shù)里,可能在異常處理程序執(zhí)行前該對象已被成功銷毀了,也就無從訪問其成員了。而類的靜態(tài)成員則沒有上述問題。
    規(guī)則15-5-1(強制)。類的析構函數(shù)退出后不能還有未處理的異常。
    當異常拋出時,會進行棧展開。如果在某個析構過程中引發(fā)沒有被處理的異常,程序將會以不定的方式終止。析構函數(shù)拋出異常的問題在很多C++的書中都有討論,概括來說:析構函數(shù)應盡可能地避免拋出異常,如果的確無法避免,則析構函數(shù)自己應該包含處理所有可能拋出的異常的代碼。
 

4 小 結
    異常機制是C++嶄新而高級的特性之一。與其他C++特性一樣,C++標準并沒有規(guī)定應該如何來實現(xiàn)異常機制,這依賴于具體的編譯器。異常機制是有代價的,它會增加代碼大小和運行開銷。以VC++為例,異常處理是通過在函數(shù)調用棧里增加許多相關的數(shù)據(jù)結構來實現(xiàn)的,感興趣的讀者可以查看相關資料,這里不再進一步討論;而且異常處理是在操作系統(tǒng)的協(xié)助下,由C++編譯器和運行時異常處理庫共同完成的。如何合理地使用異常機制來提高程序的健壯性,MISRA C++給出了一些規(guī)范,但具體還需要程序員反復斟酌,甚至需要多次實驗。至此,關于MISRA-C++:2008的學習暫告一段落。
    在這4期的講座中,我們主要討論了C++對于C新增的特性,列舉和解釋了其中有代表性的規(guī)則,且盡量使每篇文章都能涵蓋C++的一個重要特性。有些例子是在我們理解的基礎上加的,可能存在著錯誤或偏差,歡迎大家和我們共同討論。通過這4期介紹,希望大家能夠意識到:C++對于C并不是簡單的語言的改進,C++面向對象的思想從根本上影響了軟件的架構。
    可以預見,隨著嵌入式產(chǎn)業(yè)的飛速發(fā)展,在嵌入式領域C++將會有輝煌的前景。對C++進行改造,使其適用于嵌入式環(huán)境,提高其可靠性,對于推動C++在嵌入式領域的應用是很重要的。MISRA-C已經(jīng)在嵌入式C語言上取得了很大的成功,成為行業(yè)普遍認同和遵循的規(guī)范。我們希望MISRA-C++也能和MISRA-C一樣,推動C++在嵌入式領域的規(guī)范化。

本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或將催生出更大的獨角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉型技術解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關鍵字: 汽車 人工智能 智能驅動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務中斷的風險,如企業(yè)系統(tǒng)復雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務連續(xù)性,提升韌性,成...

關鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質量流程IT總裁陶景文發(fā)表了演講。

關鍵字: 華為 12nm EDA 半導體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權最終是由生態(tài)的繁榮決定的。

關鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務引領增長 以科技創(chuàng)新為引領,提升企業(yè)核心競爭力 堅持高質量發(fā)展策略,塑強核心競爭優(yōu)勢...

關鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術學會聯(lián)合牽頭組建的NVI技術創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術創(chuàng)新聯(lián)...

關鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關鍵字: BSP 信息技術
關閉
關閉