[英]Is using assert() in C++ bad practice?
我傾向於在我的 C++ 代碼中添加大量斷言,以便在不影響發布版本性能的情況下更容易調試。 現在, assert
是一個純 C 宏,設計時沒有考慮 C++ 機制。
另一方面,C++ 定義了std::logic_error
,它意味着在程序邏輯中存在錯誤的情況下拋出(因此得名)。 拋出一個實例可能只是assert
的完美的、更 C++ 風格的替代方案。
問題是assert
和abort
都立即終止程序而不調用析構函數,因此跳過清理,而手動拋出異常會增加不必要的運行時成本。 解決這個問題的一種方法是創建一個自己的斷言宏SAFE_ASSERT
,它的工作方式與 C 對應物一樣,但在失敗時拋出異常。
對於這個問題,我能想到三個意見:
#define
也同樣糟糕。NDEBUG
,這在發布版本中永遠不會發生。 捕獲是不必要的,並將內部代碼的實現細節暴露給main()
。這個問題有明確的答案嗎? 有專業參考嗎?
編輯:當然,跳過析構函數不是未定義的行為。
斷言用於調試。 您所提供代碼的用戶不應看到它們。 如果斷言被命中,你的代碼需要被修復。
該產品包含可以被攻擊者觸發的 assert() 或類似語句,這會導致應用程序退出或其他比必要更嚴重的行為。
雖然斷言有助於捕獲邏輯錯誤並減少達到更嚴重漏洞情況的機會,但它仍然可能導致拒絕服務。
例如,如果服務器同時處理多個連接,並且在一個連接中發生 assert() 導致所有其他連接被丟棄,則這是導致拒絕服務的可達斷言。
例外是針對特殊情況的。 如果遇到,用戶將無法做她想做的事,但可以在其他地方繼續。
錯誤處理適用於正常的程序流程。 例如,如果您提示用戶輸入一個數字並得到一些無法解析的信息,這是正常的,因為用戶輸入不受您的控制,您必須始終按照理所當然地處理所有可能的情況。 (例如循環直到你有一個有效的輸入,中間說“對不起,再試一次”。)
斷言完全適用於 C++ 代碼。 異常和其他錯誤處理機制並不是真正用於與斷言相同的事情。
錯誤處理適用於有可能向用戶很好地恢復或報告錯誤的情況。 例如,如果嘗試讀取輸入文件時出現錯誤,您可能需要對此做一些事情。 錯誤可能由錯誤導致,但它們也可能只是給定輸入的適當輸出。
斷言用於諸如在通常不會檢查 API 時檢查 API 要求是否得到滿足的事情,或者檢查開發人員認為他通過構造保證的事情。 例如,如果算法需要排序輸入,您通常不會檢查它,但您可能有一個斷言來檢查它,以便調試構建標記那種錯誤。 斷言應始終表明程序運行不正確。
如果您正在編寫一個程序,其中不正常關閉可能會導致問題,那么您可能希望避免斷言。 嚴格按照 C++ 語言的未定義行為在這里不屬於此類問題,因為命中斷言可能已經是未定義行為的結果,或者違反了一些其他要求,這可能會阻止某些清理工作正常進行。
此外,如果您根據異常實現斷言,那么它可能會被捕獲並“處理”,即使這與斷言的目的相矛盾。
斷言可用於驗證內部實現不變量,例如某些方法執行之前或之后的內部狀態等。如果斷言失敗,則確實意味着程序邏輯已損壞,您無法從中恢復。 在這種情況下,您能做的最好的事情就是盡快中斷而不將異常傳遞給用戶。 斷言的真正好處(至少在 Linux 上)是核心轉儲是作為進程終止的結果生成的,因此您可以輕松地調查堆棧跟蹤和變量。 這對於理解邏輯失敗比異常消息更有用。
由於 alling abort() 而沒有運行析構函數並不是未定義的行為!
如果是,那么調用std::terminate()
也是未定義的行為,那么提供它有什么意義呢?
assert()
在 C++ 中和在 C 中一樣有用。斷言不是用於錯誤處理,而是用於立即中止程序。
恕我直言,斷言是為了檢查條件,如果被違反,其他一切都是廢話。 因此,您無法從它們中恢復,或者更確切地說,恢復是無關緊要的。
我將它們分為兩類:
浮動概率(){返回-1.0; }
斷言(概率()> = 0.0)
整數 x = 1;
斷言(x > 0);
這些都是微不足道的例子,但與現實相差不遠。 例如,考慮返回負索引以用於向量的朴素算法。 或定制硬件中的嵌入式程序。 或者更確切地說,因為sh*t 發生了。
如果存在此類開發錯誤,您不應該對實施的任何恢復或錯誤處理機制充滿信心。 這同樣適用於硬件錯誤。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.