C++ 中异常的调用堆栈
今天,在我的 C++ 多平台代码中,我对每个函数都有一个 try-catch。在每个 catch 块中,我将当前函数的名称添加到异常中并再次抛出它,以便在最上面的 catch 块(我最终打印异常的详细信息)中我拥有完整的调用堆栈,这有助于我跟踪异常的原因。
这是一个很好的做法,还是有更好的方法来获取异常的调用堆栈?
Today, in my C++ multi-platform code, I have a try-catch around every function. In every catch block I add the current function's name to the exception and throw it again, so that in the upmost catch block (where I finally print the exception's details) I have the complete call stack, which helps me to trace the exception's cause.
Is it a good practice, or are there better ways to get the call stack for the exception?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
你所做的不是好的做法。原因如下:
1.没有必要。
如果您在调试模式下编译项目以便生成调试信息,您可以轻松地在 GDB 等调试器中获得异常处理的回溯。
2.很麻烦。
这是你必须记住添加到每个函数中的东西。如果您碰巧错过了某个函数,则可能会导致很大的混乱,特别是如果该函数是导致异常的函数。任何查看你的代码的人都必须意识到你在做什么。另外,我打赌您使用了诸如 __FUNC__ 或 __FUNCTION__ 或 __PRETTY_FUNCTION__ 之类的东西,遗憾的是它们都是非标准的(C++ 中没有标准方法来获取函数的名称)。
3.速度很慢。
C++ 中的异常传播已经相当慢,添加此逻辑只会使代码路径变慢。如果您使用宏来捕获和重新抛出,这不是问题,您可以轻松地在代码的发布版本中省略捕获和重新抛出。否则,性能可能会出现问题。
良好实践
虽然在每个函数中捕获并重新抛出以构建堆栈跟踪可能不是一个好习惯,但附加最初抛出异常的文件名、行号和函数名称是一个好习惯。如果您将 boost::exception 与 BOOST_THROW_EXCEPTION 一起使用,您将免费获得此行为。最好将解释信息附加到异常中,这将有助于调试和处理异常。也就是说,所有这些都应该在构造异常时发生;一旦它被构造,就应该允许它传播到它的处理程序......你不应该重复捕获和重新抛出超过严格必要的次数。如果您需要在特定函数中捕获并重新抛出以附加一些关键信息,那很好,但是捕获每个函数中的所有异常并为了附加已经可用的信息就太多了。
What you are doing is not good practice. Here's why:
1. It's unnecessary.
If you compile your project in debug mode so that debugging information gets generated, you can easily get backtraces for exception handling in a debugger such as GDB.
2. It's cumbersome.
This is something you have to remember to add to each and every function. If you happen to miss a function, that could cause a great deal of confusion, especially if that were the function that caused the exception. And anyone looking at your code would have to realize what you are doing. Also, I bet you used something like __FUNC__ or __FUNCTION__ or __PRETTY_FUNCTION__, which sadly to say are all non-standard (there is no standard way in C++ to get the name of the function).
3. It's slow.
Exception propagation in C++ is already fairly slow, and adding this logic will only make the codepath slower. This is not an issue if you are using macros to catch and rethrow, where you can easily elide the catch and rethrow in release versions of your code. Otherwise, performance could be a problem.
Good practice
While it may not be good practice to catch and rethrow in each and every function to build up a stack trace, it is good practice to attach the file name, line number, and function name at which the exception was originally thrown. If you use boost::exception with BOOST_THROW_EXCEPTION, you will get this behavior for free. It's also good to attach explanatory information to your exception that will assist in debugging and handling the exception. That said, all of this should occur at the time the exception is constructed; once it is constructed, it should be allowed to propagate to its handler... you shouldn't repeatedly catch and rethrow more than stricly necessary. If you need to catch and rethrow in a particular function to attach some crucial information, that's fine, but catching all exceptions in every function and for the purposes of attaching already available information is just too much.
不,这太可怕了,我不明白为什么在异常本身中需要调用堆栈 - 我发现异常原因、发生初始异常的代码的行号和文件名已经足够了。
话虽如此,如果您确实必须有堆栈跟踪,那么要做的就是在异常抛出站点生成一次调用堆栈信息。没有单一的便携式方法可以做到这一点,但可以使用类似 http://stacktrace.sourceforge.net/ 的方法与 VC++ 的类似库结合起来应该不会太困难。
No, it is deeply horrible, and I don't see why you need a call stack in the exception itself - I find the exception reason, the line number and the filename of the code where the initial exception occurred quite sufficient.
Having said that, if you really must have a stack trace, the thing to do is to generate the call stack info ONCE at the exception throw site. There is no single portable way of doing this, but using something like http://stacktrace.sourceforge.net/ combined with and a similar library for VC++ should not be too difficult.
一种可能更优雅的解决方案是构建一个 Tracer 宏/类。因此,在每个函数的顶部,您可以编写如下内容:
宏看起来类似于:
类 Tracer 在构造时将函数名称添加到全局堆栈中,并在销毁时删除自身。然后,该堆栈始终可用于日志记录或调试,维护更加简单(一行),并且不会产生异常开销。
实现示例包括 http://www.drdobbs.com/184405270、http://www.codeproject.com/KB/cpp/cmtrace.aspx,和 < a href="http://www.codeguru.com/cpp/vs/debug/tracing/article.php/c4429" rel="nofollow noreferrer">http://www.codeguru.com/cpp/vs/debug /tracing/article.php/c4429。像这样的 Linux 函数 http://www.linuxjournal.com/article/6391 也可以做到它更原生,如这个 Stack Overflow 问题所述: 当我的 gcc C++ 应用程序崩溃时如何生成堆栈跟踪。 ACE 的 ACE_Stack_Trace 可能也值得一看。
无论如何,异常处理方法是粗糙的、不灵活的并且计算量大。类构造/宏解决方案要快得多,并且可以根据需要进行编译以用于发布版本。
One solution which may be more graceful is to build a Tracer macro/class. So at the top of each function, you write something like:
and the macro looks something like:
and the class Tracer adds the function name to a global stack on construction, and removes itself upon destruction. Then that stack is always available to logging or debugging, maintenance is much simpler (one line), and it doesn't incur exception overhead.
Examples of implementations include things like http://www.drdobbs.com/184405270, http://www.codeproject.com/KB/cpp/cmtrace.aspx, and http://www.codeguru.com/cpp/v-s/debug/tracing/article.php/c4429. Also Linux functions like this http://www.linuxjournal.com/article/6391 can do it more natively, as described by this Stack Overflow question: How to generate a stacktrace when my gcc C++ app crashes. ACE's ACE_Stack_Trace may be worth looking at too.
Regardless, the exception-handling method is crude, inflexible, and computationally expensive. Class-construction/macro solutions are much faster and can be compiled out for release builds if desired.
所有问题的答案是一个好的调试器,通常是 http://www.gnu.org/software Linux 上的 /gdb/ 或 Windows 上的 Visual Studio。它们可以在程序中的任何点根据需要为您提供堆栈跟踪。
您当前的方法确实是一个令人头痛的性能和维护问题。调试器的发明是为了实现您的目标,但没有任何开销。
The answer to all your problems is a good debugger, usually http://www.gnu.org/software/gdb/ on linux or Visual Studio on Windows. They can give you stack traces on demand at any point in the program.
Your current method is a real performance and maintenance headache. Debuggers are invented to accomplish your goal, but without the overhead.
有一个不错的小项目,提供了漂亮的堆栈跟踪:
https://github.com/bombela/backward-cpp
There's a nice little project that gives a pretty stack trace:
https://github.com/bombela/backward-cpp
看看这个SO问题。这可能接近您正在寻找的内容。它不是跨平台的,但答案给出了 gcc 和 Visual Studio 的解决方案。
Look at this SO Question. This might be close to what you're looking for. It isn't cross-platform but the answer gives solutions for gcc and Visual Studio.
另外一个支持堆栈跟踪的项目:ex_diag。没有宏,跨平台,不需要大量代码,工具快速、清晰且易于使用。
这里只需要包装需要跟踪的对象,如果发生异常就会跟踪它们。
One more project for stack-trace support: ex_diag. There are no macros, cross-platform is present, no huge code needs, tool is fast, clear and easy in use.
Here you need only wrap objects, which are need to trace, and they will be traced if exception occurs.
与 libcsdbg 库链接(请参阅 https://stackoverflow.com/a/18959030/364818 获取原始答案)看起来像无需修改源代码或第 3 方源代码(即 STL)即可获取堆栈跟踪的最简洁方法。
这使用编译器来检测实际的堆栈集合,这正是您想要做的。
我还没有使用过它,而且它受到了 GPL 的污染,但它看起来是正确的想法。
Linking with the libcsdbg library (see https://stackoverflow.com/a/18959030/364818 for original answer) looks like the cleanest way of getting a stack trace without modifying your source code or 3rd party source code (ie STL).
This uses the compiler to instrument the actual stack collection, which is really want you want to do.
I haven't used it and it is GPL tainted, but it looks like the right idea.
虽然这里的答案提出了相当多的反驳意见,但我想指出,自从提出这个问题以来,使用C++11,添加了一些方法,可以让您获得很好的回溯以跨平台的方式,不需要调试器或繁琐的日志记录:
使用
std::nested_exception
和std:: throw_with_nested
StackOverflow 此处和这里,介绍如何通过简单地编写一个适当的异常处理程序来重新抛出嵌套异常,从而在代码中获取异常的回溯。
但是,它将要求您在要跟踪的函数处插入 try/catch 语句。
由于您可以使用任何派生异常类来执行此操作,因此您可以向此类回溯添加大量信息!
您还可以查看我的 GitHub 上的 MWE 或我的 "trace" 库,其中回溯看起来像这样:
While quite a few counter-arguments have been made in the answers here, I want to note that since this question was asked, with C++11, methods have been added which allow you to get nice backtraces in a cross-platform way and without the need for a debugger or cumbersome logging:
Use
std::nested_exception
andstd::throw_with_nested
It is described on StackOverflow here and here, how you can get a backtrace on your exceptions inside your code by simply writing a proper exception handler which will rethrow nested exceptions.
It will, however, require that you insert
try/catch
statements at the functions you wish to trace.Since you can do this with any derived exception class, you can add a lot of information to such a backtrace!
You may also take a look at my MWE on GitHub or my "trace" library, where a backtrace would look something like this:
未处理的异常将留给调用函数来处理。这将持续到异常被处理为止。无论是否在函数调用周围使用 try/catch,都会发生这种情况。换句话说,如果调用的函数不在 try 块中,则该函数中发生的异常将自动传递到调用堆栈。因此,您需要做的就是将最顶层的函数放在 try 块中,并在 catch 块中处理异常“...”。该异常将捕获所有异常。因此,您的最顶层函数将类似于
如果您想为某些异常使用特定的代码块,您也可以这样做。只需确保这些发生在“...”异常捕获块之前。
An exception that isn't handled is left for the calling function to handle. That continues until the exception is handled. This happens with or without try/catch around a function call. In other words, if a function is called that isn't in a try block, an exception that happens in that function will automatically be passed up to call stack. So, all you need to do is put the top-most function in a try block and handle the exception "..." in the catch block. That exception will catch all exceptions. So, your top-most function will look something like
If you want to have specific code blocks for certain exceptions, you can do that too. Just make sure those occur before the "..." exception catch block.