I’m not calling a virtual function from my constructor or destructor, but I’m still getting a __purecall error


Some time ago, I described what __purecall is for: It’s to detect the cases where you call a virtual method with no implementation (a so-called pure virtual method). This can happen during object constructor or destruction, since those are times when you can validly have a partially-implemented object.

Well, there’s another case this can happen: If the object has already been destructed.

If you call a method on an object that has already been destructed, the behavior is undefined. In practice, what happens is that the method runs on whatever leftover values are in memory where the object used to be. Depending on how lucky or unlucky you are, this may resemble the actual object closely enough that the method doesn’t notice, or at least doesn’t notice for a while, or maybe it can’t even get off the ground.

The __purecall error is a case where it can’t even get off the ground. The leftover object still has a vtable, namely the vtable of the base class, the one that has __purecall entries for all the pure virtual functions. (It still has that vtable because that’s the object’s last identity before finally going invalid.) And the method you tried to call is a virtual method that is pure in the base class. Not only are you calling a pure virtual function after destruction has begun, you’re calling it after destruction is complete.

Comments (21)
  1. Joshua says:

    Could be worse. At least one compiler would result in calling NULL.

  2. SimonRev says:

    Actually Joshua, I would consider that better.  A guaranteed crash right at the point of the problem is always better than undefined behavior that might seem to work sometimes.  Actually that would be a good suggestion for the MSVC compiler if it doesn't already do that for debug builds.

  3. Mc says:

    Probably a stupid question (I'm not a C++ expert)  but how does an object get destructed when something still has a live reference to it.  

  4. Adam Rosenfield says:

    @Joshua, @SimonRev: How would you expect a compiler to achieve that?  Zero out every object's memory after destruction?  That would be a huge performance hit, something you'd only want to do in a debug build.

    @Mc: Dangling references:

    MyObject *obj = new MyObject;

    delete obj;

    FunctionWhichAllocatesSomeObjectsAndWritesRandomBytesIntoTheMemoryFormerlyKnownAsObj();

    obj->BaseClassPureVirtual();  // BOOM!

  5. j b says:

    @Mc:

    Since you state yourself that you are not a C++ expert, will you allow me to guess that your are more familiar with languages with automatic garbage collection, like C# and Java?

    In C/C++ you must yourself explicitly release the memory you have allocated. There is nothing to prevent you from releasing an object to which there are still live references to it. If you do, that is your problem… (yafiygi)

  6. SimonRev says:

    @Adam:  Exactly, but not quite that extensive.  The compiler is already messing with the vtable pointer during destruction.  Just have it null out that pointer when destruction is complete.  Obviously not something you want in a release build due to performance ramifications (and I am not sure whether it would be permitted by a strictly standard compliant compiler).  But in a debug build, the cost to null out a pointer is minimal and the benefit to catching such errors may be worth it.

    @Mc — C++'s single biggest benefit over something like C# (IMO) is deterministic destruction of objects.  But it does leave you vulnerable to using already destroyed objects if you ever save a reference or pointer to one.  Adam's example is a bit obvious.  Here is a more subtle one, especially if you have a C# background.

    BaseClass & GetABaseClass() {

       DerivedClass retVal;

       return retVal;

    }

    void DoSomething() {

       BaseClass &someObj = GetABaseClass();

       someObj.VirtualMethod();

    }

    This code has undefined behavior, because retVal is destroyed at the point of return from GetABaseClass.  However, a reference lives on and gets used.  In this case, there is a very good chance that the memory pattern occupied by retVal is still intact and calling VirtualMethod would result in a __purecall violation.  However if the method were virtual but not pure virtual, then the method call might seem to succeed.

  7. Joshua says:

    @SimonRev: It filled NULLs into the vtable, so any means of calling a pure virtual function (say indirectly from base class constructor–direct call would be caught by compiler) would call NULL.

  8. MarcT says:

    Probability this will come in handy at least once in a 40-year career: 80%

    Probability I will remember this solution: 3%

    Probability I will vaguely remember Raymond said something interesting about it sometime: 15%

    Probability I would install a "Raymondizer" extension to VS that pops up during obscure errors (such as __purecall) and points to relevant Raymond Chen blog posts: 100%

  9. Danny says:

    @j b

    Even in Java or C# this still will happen.

  10. Gabe says:

    Danny: Are you suggesting that in Java or C# you could reference an object that has already been garbage-collected?

  11. SimonRev says:

    @Joshua — while I believe it could NULL out the vtable pointer for an object instance, I cannot believe it would fill the vtable itself with NULLs.  If it did that, then any live instances of the object would instantly die as all instances of the type share the same vtable.

  12. mikeb says:

    Note that in debug builds the runtime already overwrites freed dynamic memory with 0xdd bytes (which is even better than setting the memory to zero). I don't think the compiler does anything similar for automatic objects that go out of scope (though newer ones can fill uninitialized locals with 0xcc) .  There might be value in that, but it's unclear to me how much bang for buck you'd get.

  13. Using a replacement memory manager in Delphi (FastMM) we can compile with flags set that – among other useful things – cause the memory manager to write a specific bit pattern over any unallocated memory, including in destructed objects.  At runtime this will then result in errors such as "Attempt to call method on a destroyed object)", along with a stack trace of the attempted call and a stack trace of the destruction of the object.

    If you inspect such an object (memory reference) using a bad reference in the debugger, it's classname is reported as a "TFreedObject".

    So finding such errors during development is made much easier (not bullet proof, as it is still possible that such deallocated memory might be re-allocated before the bad reference is used).

    Neat huh ?  :)

    Well, we can do this as long as Embarcadero don't rewrite the EULA to stop us.  :D

  14. Drak says:

    @Gabe: well you can access an object that has been 'disposed'. But I believe this is the step just before it gets collected, so technically it's not collected yet.

  15. Danny says:

    @Gabe

    Yes, that's exactly what I am saying. Mind you, we talk about object reference here, not object access!

  16. Ooh says:

    @Gabe, Drak: Beyond disposed objects (which still have references to them and thus don't qualify for garbage collection) something called object resurrection can happen.

    Situation: You have a finalizable object, i.e. the object's type declares a finalizer. When the object isn't referenced any more, the garbage collector runs the finalizer. In the finalizer code, a new reference to the object is added. Then the object survives garbage collection; it resurrects.

  17. Gabe says:

    Danny: I don't know what you mean. If I have a reference to an object, I can access it. The GC only collects objects that have no references, so there's no way to have a reference to a GCed object.

    Ooh alludes to a way to have a reference to an object that has had its finalizer called, but such an object would still not have been GCed.

  18. Ben says:

    I remeber hitting this some time ago with a "role your own" smartpointer implementation that would do an implicit upcast resulting in the destruction of the underlying object (which is why boost et al prevent this). The annoying thing about the pure virtual function call exception is that it throws up a dialog box! Instead of crashing, as would be the sane thing to do, and providing a nice call stack, you get a dialog that stops the program waiting for user input! What is the user going to do? Attach a debugger? (yes I am aware that it is possible to add your own handler to avoid this issue. Now I am). In this specific situation it was made worse because the program would continue to drive itself via the window message pump! We had just crashed, but kept right on going! We got a report from a customer saying "why do we get this strange dialog?, if I ignore it every keeps on working." To decide to throw a dialog for a pure virtual function call was an insane idea; time to grab the torches and pitchforks people! 

  19. Gabe says:

    Ben: If your program just kept on working, it didn't really crash now, did it?

  20. fdiv says:

    @Gabe: Many times it can be hard to determine if the program, or a complex system in general, is buggy. That's why the Pentium fdiv bug wasn't discovered immediately, it was only a fractional bug.

  21. Gabe says:

    fdiv: What Ben was describing was more of a fender-bender than a crash. While I understand that a car crash necessarily means that I may not make it to my destination, I'm glad that every fender-bender or check-engine light doesn't delay my trip any more than absolutely necessary.

Comments are closed.