簡體   English   中英

C++ 中的異常調用堆棧

[英]Call-stack for exceptions in C++

今天,在我的 C++ 多平台代碼中,我對每個函數都有一個 try-catch。 在每個 catch 塊中,我將當前函數的名稱添加到異常中並再次拋出它,因此在最上面的 catch 塊中(我最終打印異常的詳細信息)我有完整的調用堆棧,這有助於我跟蹤異常的原因。

這是一個好的做法,還是有更好的方法來獲取異常的調用堆棧?

你在做什么不是好的做法。 原因如下:

1.沒必要。
如果您在調試模式下編譯您的項目以便生成調試信息,您可以輕松地在調試器(如 GDB)中獲取異常處理的回溯。

2. 很麻煩。
這是你必須記住添加到每個函數的東西。 如果你碰巧錯過了一個函數,那可能會造成很大的混亂,尤其是當那個函數是導致異常的時候。 任何查看您的代碼的人都必須意識到您在做什么。 另外,我敢打賭你使用了 __FUNC__ 或 __FUNCTION__ 或 __PRETTY_FUNCTION__ 之類的東西,遺憾的是它們都是非標准的(C++ 中沒有標准的方法來獲取函數的名稱)。

3.它很慢。
C++ 中的異常傳播已經相當緩慢,添加此邏輯只會使代碼路徑變慢。 如果您使用宏來捕捉和重新拋出,這不是問題,您可以在代碼的發布版本中輕松地省略捕捉和重新拋出。 否則,性能可能會成為問題。

好的做法
雖然捕獲並重新拋出每個函數以構建堆棧跟蹤可能不是一個好習慣,但最好附上最初拋出異常的文件名、行號和函數名。 如果您將 boost::exception 與 BOOST_THROW_EXCEPTION 一起使用,您將免費獲得此行為。 將有助於調試和處理異常的解釋性信息附加到您的異常也很好。 也就是說,所有這些都應該在構造異常時發生; 一旦它被構建,它應該被允許傳播到它的處理程序......你不應該重復捕獲和重新拋出超過嚴格必要的次數。 如果您需要在特定函數中捕獲並重新拋出以附加一些關鍵信息,那很好,但是捕獲每個函數中的所有異常並為了附加已經可用的信息就太多了。

不,這太可怕了,我不明白為什么你需要在異常本身中調用堆棧——我發現異常原因、最初發生異常的代碼的行號和文件名就足夠了。

話雖如此,如果您真的必須有一個堆棧跟蹤,那么要做的就是在異常拋出站點生成一次調用堆棧信息。 沒有單一的可移植方法可以做到這一點,但是使用類似http://stacktrace.sourceforge.net/的東西以及類似的 VC++ 庫應該不會太困難。

一種可能更優雅的解決方案是構建 Tracer 宏/類。 所以在每個函數的頂部,你寫這樣的東西:

TRACE()

宏看起來像:

Tracer t(__FUNCTION__);

類 Tracer 在構建時將函數名稱添加到全局堆棧,並在銷毀時刪除自身。 然后該堆棧始終可用於日志記錄或調試,維護簡單得多(一行),並且不會產生異常開銷。

實施示例包括http://www.drdobbs.com/184405270、http://www.codeproject.com/KB/cpp/cmtrace.aspxhttp://www.codeguru.com/cpp/vs/調試/跟蹤/article.php/c4429 此外,像http://www.linuxjournal.com/article/6391這樣的 Linux 函數可以更自然地完成它,正如這個 Stack Overflow 問題所描述的: How to generate a stacktrace when my gcc C++ app crashes ACE 的 ACE_Stack_Trace 也值得一看。

無論如何,異常處理方法是粗糙的、不靈活的並且計算量大。 類構造/宏解決方案要快得多,並且可以根據需要編譯出來用於發布版本。

有一個不錯的小項目可以提供漂亮的堆棧跟蹤:

https://github.com/bombela/backward-cpp

您所有問題的答案是一個好的調試器,在 Linux 上通常是http://www.gnu.org/software/gdb/或在 Windows 上是 Visual Studio。 他們可以在程序的任何一點按需為您提供堆棧跟蹤。

您當前的方法是一個真正的性能和維護問題。 發明調試器是為了實現您的目標,但沒有開銷。

看看這個SO 問題 這可能接近您要查找的內容。 它不是跨平台的,但答案為 gcc 和 Visual Studio 提供了解決方案。

另一個支持堆棧跟蹤的項目: ex_diag 沒有宏,存在跨平台,不需要大量代碼,工具快速、清晰且易於使用。

這里只需要包裝需要跟蹤的對象,如果出現異常就會跟蹤到。

與 libcsdbg 庫鏈接(請參閱https://stackoverflow.com/a/18959030/364818獲取原始答案)看起來是無需修改源代碼或第 3 方源代碼(即 STL)即可獲取堆棧跟蹤的最簡潔方法。

這使用編譯器來檢測實際的堆棧集合,這是您真正想要做的。

我沒用過它,它被 GPL 污染了,但它看起來是個正確的主意。

雖然這里的答案中提出了很多反駁論點,但我想指出的是,自從提出這個問題以來, C++11已經添加了一些方法,這些方法允許您以跨平台的方式獲得很好的回溯,並且無需調試器或繁瑣的日志記錄:

使用std::nested_exceptionstd::throw_with_nested

它在 StackOverflow herehere上進行了描述,您如何通過簡單地編寫一個將重新拋出嵌套異常的適當異常處理程序來獲取代碼中異常的回溯 但是,它會要求您在要跟蹤的函數中插入try/catch語句。

由於您可以對任何派生的異常類執行此操作,因此您可以向此類回溯添加大量信息! 您還可以查看我在 GitHub 上的 MWE或我的“trace”庫,其中回溯看起來像這樣:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

未處理的異常留給調用函數處理。 這一直持續到處理異常為止。 無論有沒有 try/catch 函數調用都會發生這種情況。 換句話說,如果調用的函數不在 try 塊中,則該函數中發生的異常將自動向上傳遞到調用堆棧。 因此,您需要做的就是將最頂層的函數放在 try 塊中,並在 catch 塊中處理異常“...”。 該異常將捕獲所有異常。 所以,你最頂層的功能看起來像

int main()
{
  try
  {
    top_most_func()
  }
  catch(...)
  {
    // handle all exceptions here
  }
}

如果你想為某些異常使用特定的代碼塊,你也可以這樣做。 只要確保那些發生在“...”異常捕獲塊之前。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM