C 錯誤處理方式的思考

2021-08-09 21:33:41 字數 2904 閱讀 8406

前言:

介紹了異常機制在專案中的應用,同時介紹了scopeexit和用無限展開巨集技術寫的乙個errert

:知乎上中對異常的討論:

一.錯誤處理要思考的四個問題:

1.錯誤的定義,即什麼是錯誤;

2.如何報告錯誤?error-code機制還是異常機制?

3.何時,如何處理錯誤

二.什麼是錯誤

錯誤即是當程式執行到某處時,不應該出現的情況,在debug版本中,你通常會用assert()去處理的點,你相信此處不會出現意料之外的事情,但卻偏偏發生了。一些最常見的例子:調

用方傳入的引數不滿足被呼叫方的precondition;你要開啟的檔案找不到或者是沒有許可權開啟;資料庫訪問出錯,網路傳輸出錯,資源獲取失敗等等。

這些例子都有乙個共同的特點就是,會導致程式無法繼續正常的執行下去,或者不知如何轉到其他的執行路徑中:

a.呼叫方傳入的引數無法滿足被呼叫方的preconditin,例如會導致陣列越界,這時是肯定無法正常的執行程式了,只能是報告該錯誤,讓程式處於乙個能暴露該錯誤的狀態,例如強制退出。            

b.資源獲取失敗,例如new()失敗了,這時還能怎麼處理?肯定是最善後清理工作,然後讓程式退出了。

c.資料庫訪問出錯,網路傳輸出錯,不一定會導致整個程式失敗,但至少某部分的功能是不能正常的使用了。

這些例子都說明了錯誤的顯著特徵,這是和普通的狀態碼標識的某些狀態區分開來的。因為在用error-code的時候,很多人都把函式的返回值誤以為是error-code,其實不然,很多返回值應該叫status-code,它只是用來表示程式的不同執行狀態,通常用來作為程式控制流使用:

if(foo())

else

error-code和status-code都是使用同樣的乙個通道,即函式的返回值,這才導致了很多人沒有注意到應該把這兩個東西區分開來。之所以特意提到這一點,是想強調,錯誤處理機制是用來匯報錯誤的,而不是用來控制程式流的。當想把乙個系統的錯誤處理機制從error-code換成異常機制時,特別需要注意這一點。很多人把error-code和status-code都用異常來替換,然後就導致了 throw,try,catch遍布各處,然後就抱怨c++的異常機制有各種問題:資源洩露,事務語意難以保證等異常安全問題。根本問題就是異常機制被亂用了而已。

要處理好錯誤本質上來說都是困難的,而跟使用error-code還是異常機制無關,我們無非是想討論一些在哪些情況下用哪種機制會相對方便一點而已。

三.如何報告錯誤

error-code還是異常?這是很多c++程式設計師爭論的點。error-code的特點:

1.用函式返回值作為傳遞錯誤的通道,相容c的介面

2.如果需要將錯誤往上層傳遞時,需要一層層的檢查函式的返回值,顯式傳遞給上一層;否則的話,將會帶著錯誤狀態往下執行程式流,可能會在接下來的執行流的某處,程式就莫名奇妙的爆掉了,這時離原始的錯誤點已經很遠,帶來的代價是很難找到錯誤的根本原因了。一階錯誤沒處理好將導致二階三階問題的出現。

3.從錯誤發生點到最終的錯誤處理點的需要一層層通過return返回。這種方式的優點是,在工程中或在code-review的時候,更容易發現錯誤安全的問題,例如函式退出時有沒有釋放掉之前申請的資源。但是缺點也很明顯,首先是繁瑣,需在每一層函式中寫錯誤檢查**;第二是如果在某一層忘了寫錯誤檢查**,則錯誤有可能被忽略掉了。

異常機制的特點:

1.單獨的錯誤匯報通道,並且更容易攜帶更豐富的資訊,不相容c的介面。

2.錯誤資訊不會被忽略,會自動的一層層往上傳遞直到被catch。優點是更容易將錯誤傳遞到上層進行統一處理;但是如果沒有做好資源的自動管理,則很難做好資源洩露問題,因為函式的退出是隱式的。

3.在建構函式或者其他一些返回值固定的函式,例如操作符過載中,更容易匯報錯誤

4.**中不需要很多錯誤檢查的**,而是只有真正的功能**,減少噪音。

我個人的看法是,乙個系統中,如果錯誤發生後,能夠立即被處理或者在比較近的層次中被處理,而無需被傳到上層,那可以用error-code的方式;

如果錯誤發生後並不能被立即處理,而是只有比較上層的層次才知道如何處理錯誤,那麼就應該用異常機制,這不僅是考慮到錯誤能夠更方便的被傳遞,少寫一些**,更大的原因是錯誤不會在傳遞中被忽略掉。在這種情況下,因為錯誤發生點在呼叫層的深處,程式設計師可能就會懶得每一層都進行檢查了。

當然,如果用了異常機制,那麼應該配合用raii手法進行資源管理。不管是用error-code的方式還是用異常機制,都會有錯誤安全的問題,這是錯誤處理的本質問題。但是有乙個區別就是,一旦用了異常機制,毫無疑問的只有用了raii手法才能減輕資源管理的負擔。(不止記憶體資源,還包括資料庫連線,檔案控制代碼等資源)

四.何時,如何處理錯誤

這個問題首先應該講錯誤分成兩類:可恢復的錯誤和不可恢復的錯誤。

對於可恢復錯誤:

(1)在發生點立即恢復錯誤,就像錯誤從來沒發生一樣。例如,配置檔案缺失,則建立乙個預設配置值;資源獲取失敗,啟用備用資源等等;這類錯誤恢復的關鍵點是當前的函式有足夠的環境上下文,知道恢復錯誤所需的所有資料。

(2)回滾到上層函式棧中進行恢復。這種錯誤回滾一般在某個層次將整個函式棧的執行看成一組事務,如果錯誤則整組事務撤銷,恢復資料或者引導程式到一條備用路徑上執行。

對於不可恢復錯誤:

這時就沒辦法了,只能退出當前模組或者程式掛掉了。在退出模組之前必須進行關鍵資料的儲存,申請資源的釋放(包括網路埠,資料庫鏈結等資源),日誌的記錄等善後工作。而且,這些善後工作一般會在程式的頂層位置進行處理,如果是在乙個內聚性比較強的功能模組中,則可以在模組的呼叫邊界處放乙個catch(...)捕捉所有的異常,進行統一的善後工作,同時防止模組內的異常洩露到模組之外。

最後一點,保證異常安全的強有力工具當然是raii了,具有的用法可以查詢相關資料獲取。

C 記憶體分配方式與錯誤處理方式

c 記憶體分配方式有三種 1 從靜態儲存區域分配。內存在程式編譯的時候就已經分配好,這塊內存在程式的整個執行期間都存在。例如全域性變數,static 變數。2 在棧上建立。在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器的指...

C 錯誤處理

語言不提供對錯誤處理的直接支援,但是作為一種系統程式語言,它以返回值的形式允許您訪問底層資料。在發生錯誤時,大多數的 c 或 unix 函式呼叫返回 1 或 null,同時會設定乙個錯誤 errno,該錯誤 是全域性變數,表示在函式呼叫期間發生了錯誤。您可以在 errno.h 標頭檔案中找到各種各樣...

MySql錯誤處理 錯誤處理的例子

有幾種錯誤處理的宣告形式 如果任何錯誤 不是 not found 設定 l error 為 1 後繼續執行 declare continue handler for sqlexception set l error 1 如果發生任何錯誤 不是 not found 執行 rollback和產生一條錯誤...