Call-stack for exceptions in C++
今天,在我的C++多平台代码中,我尝试捕捉每个函数。在每个catch块中,我将当前函数的名称添加到异常中,然后再次抛出它,这样在最上面的catch块中(在这里我最终打印了异常的详细信息),我就有了完整的调用堆栈,这有助于跟踪异常的原因。
这是一个好的实践,还是有更好的方法来获取异常的调用堆栈?
不,这是非常可怕的,我不明白为什么您需要在异常本身中有一个调用堆栈-我发现异常的原因、行号和代码的文件名,在这些代码中初始异常发生的地方已经足够了。
尽管如此,如果您真的必须有一个堆栈跟踪,那么要做的就是在异常抛出站点生成一次调用堆栈信息。没有一种可移植的方法可以做到这一点,但是将http://stacktrace.sourceforge.net/与类似的VC++库结合使用应该不会太困难。
你所做的不是很好的练习。这就是为什么:
1。这是不必要的。
如果您在调试模式下编译项目以便生成调试信息,那么您可以很容易地在调试程序(如gdb)中获取异常处理的回溯。
2。太麻烦了。
这是您必须记住的,必须添加到每个函数中。如果您碰巧错过了一个函数,这可能会导致大量的混乱,特别是如果这是导致异常的函数。任何看过你的代码的人都必须意识到你在做什么。此外,我敢打赌,你使用了一些类似于函数的函数,比如说函数,或者函数,函数,它们都是非标准的(C++中没有标准的方法来获得函数的名称)。
三。很慢。
C++中的异常传播已经相当慢了,添加这个逻辑只会使代码路径变慢。如果您使用宏来捕获和重发代码,那么这不是一个问题,您可以在代码的发布版本中轻松地忽略捕获和重发。否则,性能可能是一个问题。
良好实践
虽然在每个函数中捕获并重新执行以建立堆栈跟踪可能不是一个好的实践,但最好附加最初引发异常的文件名、行号和函数名。如果使用boost::exception和boost抛出exception,您将免费获得此行为。在异常中附加解释性信息也很好,这将有助于调试和处理异常。也就是说,所有这些都应该在构造异常时发生;一旦构造了异常,就应该允许它传播到它的处理程序…你不应该反复地抓和再抓。如果您需要捕获并重新执行某个特定的函数来附加一些关键信息,这是很好的,但是捕获每个函数中的所有异常以及为了附加已经可用的信息而附加的所有异常都太多了。
一个更为优雅的解决方案是构建一个跟踪程序宏/类。因此,在每个函数的顶部,编写如下内容:
1 | TRACE() |
宏看起来像:
1 | Tracer t(__FUNCTION__); |
类跟踪程序在构造时将函数名添加到全局堆栈中,并在销毁时将自身移除。然后,该堆栈总是可用于日志记录或调试,维护更简单(一行),并且不会产生异常开销。
实现示例包括http://www.drdobbs.com/184405270、http://www.codeproject.com/kb/cpp/cmtrace.aspx和http://www.codeguru.com/cpp/v-s/debug/tracing/article.php/c4429。同样,像这样的Linux函数HTTP://www. LINUXJUNAL.COM/TUNELY/691可以更原生地做这件事,正如这个堆栈溢出问题所描述的:当我的GCC C++应用程序崩溃时,如何生成堆栈跟踪。ace的ace堆栈跟踪可能也值得一看。
无论如何,异常处理方法是粗糙的、不灵活的,并且计算成本很高。类构造/宏解决方案速度更快,如果需要,可以编译出来用于发布版本。
所有问题的答案都是一个好的调试器,通常是Linux上的http://www.gnu.org/software/gdb/或Windows上的Visual Studio。它们可以在程序中的任何点按需提供堆栈跟踪。
您当前的方法是一个真正的性能和维护头痛。调试器是为了实现您的目标而发明的,但是没有开销。
看看这个问题。这可能接近你要找的。它不是跨平台的,但答案给出了GCC和Visual Studio的解决方案。
与libcsdbg库的链接(原始答案请参见https://stackoverflow.com/a/18959030/364818)看起来是在不修改源代码或第三方源代码(即stl)的情况下获取堆栈跟踪的最干净的方法。
它使用编译器来检测实际的堆栈集合,这是您真正想要做的。
我没有用过它,而且它被GPL污染了,但它看起来是正确的想法。
堆栈跟踪支持的另一个项目:ex_diag。没有宏,存在跨平台,不需要庞大的代码,工具快速、清晰且易于使用。
这里您只需要包装需要跟踪的对象,如果发生异常,它们将被跟踪。
有一个很好的小项目可以提供一个很好的堆栈跟踪:
https://github.com/bobela/backward-cpp
未处理的异常留给调用函数处理。直到异常处理完毕。无论函数调用周围是否有try/catch,都会发生这种情况。换句话说,如果调用的函数不在try块中,则该函数中发生的异常将自动传递到调用堆栈。所以,您需要做的就是将最顶层的函数放在一个try块中,并处理catch块中的异常"…"。该异常将捕获所有异常。所以,你最上面的函数看起来
1 2 3 4 5 6 7 8 9 10 11 | int main() { try { top_most_func() } catch(...) { // handle all exceptions here } } |
如果要为某些异常设置特定的代码块,也可以这样做。只需确保这些发生在"…"异常catch块之前。