Difference between revisions of "Stacktrace"

From SfzWiki
Jump to: navigation, search
 
 
Line 1: Line 1:
Das ganze stammt von diesem Blog http://www.premortal.com/weblog/index.php?p=28
+
copyed from this blog : http://www.premortal.com/weblog/index.php?p=28
  
 
<pre>
 
<pre>

Latest revision as of 17:11, 22 November 2005

copyed from this blog : http://www.premortal.com/weblog/index.php?p=28

Today I was discussing about how one could implement a stacktrace in C++, 
where one has not the luxury of Thread.dumpStack() or Throwable.printStackTrace(…) of Java. 
The general C++ approach one finds often is to create a dummy object on the stack 
at the beginning of each method which receives the current file and function as 
constructor parameters (using the __FILE__, __FUNCTION__ and __LINE__ macros) and 
stores them, i.e. increases a list pointer and saves the const char* at the 
resulting position. As soon as the object gets destructed at the end of the function, 
the list header pointer is decreased again. So, my first implementation looked like this:

#ifndef FINAL
#define CALLSTACK CallStack dummy(__FILE__, __FUNCTION__)
#else
#define CALLSTACK
#endif
class CallStack
{
public:
    CallStack( const char* _file, const char* _function )
    {
        file[ current ] = _file;
        function[ current ] = _function;
        current++;
    }
    ~CallStack()
    {
        current--;
    }
    static void dump()
    {
        for( int i = current - 1; i >= 0; --i )
        {
            std::cout < < file[ i ] << ": " << function[ i ] << std::endl;
        }
    }
private:
    static int current;
    static const int MAX_STACK_DEPTH = 1024;
    static const char* file[ MAX_STACK_DEPTH ];
    static const char* function[ MAX_STACK_DEPTH ];
};
int CallStack::current = 0;

The first test with an exception I threw somewhere deep in the call hierarchy 
of my program revealed what one has to remember about exceptions: objects that 
exist on the stack at the time the exception is thrown are ordinary destructed. 
So, the d’tor of my CallStack object was called, too, and current was not pointing 
to where I had expected. So, I had to mark the last element of my list in order to 
recognize it as the tail. I decided to set the following element after current to 0 
so that I would iterate through the elements until I reached 0 in order to dump the stack trace:

    CallStack( const char* _file, const char* _function )
    {
        file[ current ] = _file;
        function[ current ] = _function;
        current++;
        file[ current ] = 0;
        function[ current ] = 0;
    }

I thought this would work fine, but a friend of mine pointed out this case:

void someMethod()
{
    CALLSTACK;
    foo(); // foo was called, end of list is still behind foo
    throw; // exception is thrown and the resulting stacktrace is misleading
}

So, it turned out that I had to move the tail-pointer back, too, but only in case 
no exception was thrown. Someone on flipcode had a solution which required all 
exceptions to descend from one base class which handled this situation, 
but I wanted to be able to throw anything. bool std::uncaught_exception() satisfied my needs. 
So, if we’re in the CallStack d’tor, and std::uncaught_exception() returns true, 
we know that the current method just caused an exception, so we don’t need to move 
the tail marker anymore. It’s just that simple. 
I used a static bool flag bool CallStack::exc = false; to remember this situation for 
the following d’tors. Of course, after one handled an exception one should reset 
the flag so that the following d’tors set the tail marker correctly again. 
One should also check whether MAX_STACK_DEPTH is reached (or use std::vector), 
but you’ll figure this out yourself.