Call-stack for exceptions in C++

26,655

Solution 1

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.

Solution 2

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:

TRACE()

and the macro looks something like:

Tracer t(__FUNCTION__);

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.

Solution 3

There's a nice little project that gives a pretty stack trace:

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

Solution 4

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.

Solution 5

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.

Share:
26,655
Tachikoma
Author by

Tachikoma

Software Engineer

Updated on December 25, 2020

Comments

  • Tachikoma
    Tachikoma over 3 years

    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?

  • Oddthinking
    Oddthinking almost 14 years
    Debuggers are great at solving reproducible problems. Intermittent bugs (especially those that occur in the field) are the ones where stack-traces are beneficial.
  • Oddthinking
    Oddthinking almost 14 years
    This doesn't solve the problem of how to generate a stack-trace to help work out why the exception was raised.
  • Ozkan
    Ozkan almost 14 years
    To add to the "It's slow" case, it also prevents tail-position call optimizations by the compiler.
  • Admin
    Admin almost 14 years
    Actually, the overhead if no exception actually occurs is normally pretty small, and if it does occur (which should be rare) typically is not very important.
  • Ben Voigt
    Ben Voigt almost 14 years
    Crash dump + debugger > stack trace
  • Michael Aaron Safyan
    Michael Aaron Safyan almost 14 years
    @Neil, I was referring to the case where an exception propagates.
  • Puppy
    Puppy almost 14 years
    There are zero-cost try implementations, I believe.
  • Admin
    Admin almost 14 years
    @DeadMG If so, I've never encountered one. A reference, please.
  • Puppy
    Puppy almost 14 years
    @Neil: I've no idea. That's why I believe, rather than I know :P It's merely something that I heard.
  • Martin York
    Martin York almost 14 years
    @DeadMG: Urban legends are not worth repeating as it reinforces them with no facts. In the beginning C++ exceptions where slow (probably otherwise the myths would not have started) no doubt, but with modern compilers and computers I have never had a problem with speed that has caused my to doubt that exceptions were more than fast enough (and I work with highly scalable SOA were response times are very important). So though I have no proof either way I ignore mythes and will not consider exception speed a problem until I find an explicit case where it is.
  • Michael Aaron Safyan
    Michael Aaron Safyan almost 14 years
    @Martin, exceptions are fast enough that they are worth using, but objects need to be destructed when exceptions propagate, so the speed of propagation can't be any faster than the destruction.
  • Martin York
    Martin York almost 14 years
    @Michael Aaron Safyan: The objects need to be destroyed weather exceptions are used or not. So this is a zero sum equation.
  • CashCow
    CashCow about 13 years
    If you use this to build your exception and potentially only in debug mode. It would be nice to be able to add parameter information of course.
  • Dan
    Dan over 10 years
    It's worth noting that even after an exception destructors are called for all constructed objects. This means unless you print the global stack in the function having the exception this model will unravel just like the normal call stack. That being said I still decided to use it but I don't remove things from the stack to avoid the unraveling. I just know that the last thing in the stack is where the error happened (or the closest trace to it). I also added a depth counter that increments on construction and decrements on destruction for tabbing like the example. All in all a good Idea.
  • CashCow
    CashCow over 10 years
    @Dan yes it will require you to use the trace when you throw and put it into the message. The normal catcher will then be able to see what trace was generated up to that point.
  • CashCow
    CashCow over 10 years
    As for "global stack" it would of course need to be thread-based stack should your code be multi-threaded.
  • Yakov Galka
    Yakov Galka about 8 years
    boost::exception is such a heavy beast that saying that you "get this behavior for free" is somewhat moot.
  • LanYi
    LanYi over 4 years
    But what about exceptions not thrown by myself, for example exceptions thrown from standard library? Unless I wrap every line of standard library calls with a try-catch-throw, how can I know where the exception has been triggered? There could be thousands of at() which could throw exceptions.