I spent about two hours today trying to debug a race condition in a multi-threaded C++ app today… definitely not a fun thing to do. The worst part? The runtime diagnostics weren’t giving me anything useful to work with! Sometimes things just worked, sometimes I got segmentation faults inside old, well-tested parts of the application. At one point, I saw this error pop up:
pure virtual method called
terminate called without an active exception
Aborted
What? I know I can’t instantiate a class that has any pure-virtual methods, so how did this
error show up? To debug it, I decided to replace all of the
potentially-erroneous pure virtuals with stub functions that printed
warnings to stderr
. Lo and behold, I confirmed that polymorphism wasn’t working in my application. I had a bunch of Derived
s sitting in memory, and yet, the Base
methods were being called.
Why was this happening? Because I was deleting objects while they were still in use.
I don’t know if this is GCC-specific or not, but something very curious
happens inside of destructors. Because the object hierarchy’s
destructors get called from most-derived to least-derived, the object’s
vtable
switches up through parent classes. As a result, at some point in time (nondeterministic from a separate thread), my Derived
objects were all really Base
s. Calling a virtual member function on them in this mid-destruction state is what caused this situation.
Here’s about the simplest example I can think of that reproduces this situation:
#include
#include
struct base
{
virtual ~base() { sleep(1); }
virtual void func() = 0;
};
struct derived : public base
{
virtual ~derived() { }
virtual void func() { return; }
};
static void *thread_func(void* v)
{
base *b = reinterpret_cast (v);
while (true) b->func();
return 0;
}
int main()
{
pthread_t t;
base *b = new derived();
pthread_create(&t, 0, thread_func, b);
delete b;
return 0;
}
So what’s the moral of the story? If you ever see the error message pure virtual method called / terminate called without an active exception, check your object lifetimes! You may be trying to call members on a destructing (and thus incomplete) object. Don’t waste as much time as I did.
I ran into another scenario: automatic variable. When it was passed as an argument to pthread_create(), it was still there, but because execution of pthread_create() may be delayed, the automatic variable–a derived object–ran off the end of the (member) function.
I fixed it by using a member variable, which obviously lives longer than that function.
I meant execution of the thread made by pthread_create() may be delayed, not itself.