Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where do "pure virtual function call" crashes come from?

People also ask

What happens when a pure virtual function is called?

Member functions can be called from a constructor (or destructor) of an abstract class; the effect of making a virtual call (10.3) to a pure virtual function directly or indirectly for the object being created (or destroyed) from such a constructor (or destructor) is undefined.

Can a pure virtual function be called?

A pure virtual function is declared, but not necessarily defined, by a base class. A class with a pure virtual function is "abstract" (as opposed to "concrete"), in that it's not possible to create instances of that class.

What happens if you call a pure virtual function C++?

Calling a pure virtual function from a constructor is undefined behaviour even if it has an implementation.


They can result if you try to make a virtual function call from a constructor or destructor. Since you can't make a virtual function call from a constructor or destructor (the derived class object hasn't been constructed or has already been destroyed), it calls the base class version, which in the case of a pure virtual function, doesn't exist.

(See live demo here)

class Base
{
public:
    Base() { doIt(); }  // DON'T DO THIS
    virtual void doIt() = 0;
};

void Base::doIt()
{
    std::cout<<"Is it fine to call pure virtual function from constructor?";
}

class Derived : public Base
{
    void doIt() {}
};

int main(void)
{
    Derived d;  // This will cause "pure virtual function call" error
}

As well as the standard case of calling a virtual function from the constructor or destructor of an object with pure virtual functions you can also get a pure virtual function call (on MSVC at least) if you call a virtual function after the object has been destroyed. Obviously this is a pretty bad thing to try and do but if you're working with abstract classes as interfaces and you mess up then it's something that you might see. It's possibly more likely if you're using referenced counted interfaces and you have a ref count bug or if you have an object use/object destruction race condition in a multi-threaded program... The thing about these kinds of purecall is that it's often less easy to fathom out what's going on as a check for the 'usual suspects' of virtual calls in ctor and dtor will come up clean.

To help with debugging these kinds of problems you can, in various versions of MSVC, replace the runtime library's purecall handler. You do this by providing your own function with this signature:

int __cdecl _purecall(void)

and linking it before you link the runtime library. This gives YOU control of what happens when a purecall is detected. Once you have control you can do something more useful than the standard handler. I have a handler that can provide a stack trace of where the purecall happened; see here: http://www.lenholgate.com/blog/2006/01/purecall.html for more details.

(Note you can also call _set_purecall_handler() to install your handler in some versions of MSVC).


Usually when you call a virtual function through a dangling pointer--most likely the instance has already been destroyed.

There can be more "creative" reasons, too: maybe you've managed to slice off the part of your object where the virtual function was implemented. But usually it's just that the instance has already been destroyed.


I ran into the scenario that the pure virtual functions gets called because of destroyed objects, Len Holgate already have a very nice answer, I would like to add some color with an example:

  1. A Derived object is created, and the pointer (as Base class) is saved somewhere
  2. The Derived object is deleted, but somehow the pointer is still referenced
  3. The pointer which points to deleted Derived object gets called

The Derived class destructor reset the vptr points to the Base class vtable, which has the pure virtual function, so when we call the virtual function, it actually calls into the pure virutal ones.

This could happen because of an obvious code bug, or a complicated scenario of race condition in multi-threading environments.

Here is an simple example (g++ compile with optimization turned off - a simple program could be easily optimized away):

 #include <iostream>
 using namespace std;

 char pool[256];

 struct Base
 {
     virtual void foo() = 0;
     virtual ~Base(){};
 };

 struct Derived: public Base
 {
     virtual void foo() override { cout <<"Derived::foo()" << endl;}
 };

 int main()
 {
     auto* pd = new (pool) Derived();
     Base* pb = pd;
     pd->~Derived();
     pb->foo();
 }

And the stack trace looks like:

#0  0x00007ffff7499428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007ffff749b02a in __GI_abort () at abort.c:89
#2  0x00007ffff7ad78f7 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007ffff7adda46 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007ffff7adda81 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x00007ffff7ade84f in __cxa_pure_virtual () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x0000000000400f82 in main () at purev.C:22

Highlight:

if the object is fully deleted, meaning destructor gets called, and memroy gets reclaimed, we may simply get a Segmentation fault as the memory has returned to the operating system, and the program just can't access it. So this "pure virtual function call" scenario usually happens when the object is allocated on the memory pool, while an object is deleted, the underlying memory is actually not reclaimed by OS, it is still there accessible by the process.