CRT virtual destructor
Asked Answered
F

3

6

I ran into a heap corruption today caused by different CRT settings (MTd MDd) in my dll and my actual project. What I found strange is that the application only crashed when I set the destructor in the dll to be virtual. Is there an easy explanation for that? I get that I can't free memory that's not on my heap, but where exactly is the difference when I define the destructor as non-virtual.

Some Code just to make it a little clearer

The DLL

#pragma once
class CTestClass
{
public:
    _declspec(dllexport) CTestClass() {};
    _declspec(dllexport) virtual ~CTestClass() {};
};

And my project

int main(int argc, char* argv[])
{
    CTestClass *foo = new CTestClass;
    delete foo; // Crashes if the destructor is virtual but works if it's not
}
Forrest answered 8/7, 2013 at 16:28 Comment(3)
ALso, do you have the same problem by moving the declspec to the class (class _declspec(dllexport) CTestClass {...}) and remove the per-member declspecs ? Just curious. And note, the calling code and the DLL should be using the same CRT (debug or release), so thats something to consider. I'm not even sure mixed-modes is supported (I don't think it is).Homosporous
You've got multiple copies of the CRT in your process. And you export just the class methods, not the v-table. Trying to reason out how this all interacts to bomb your code isn't that productive, it is expected. Exporting a class with virtual methods requires you to export the entire class, put __declspec(dllexport) next to the class keyword. And you must ensure a single allocator is used to create and destroy the object. Very hard to guarantee unless you build with /MD consistently and use the exact same compiler version. Exposing C++ classes across module boundaries is just risky.Schizomycete
You are propably right, even if i figure out why it doesn't work, it won't help me too much. Thanks anyway for your thoughts :)Forrest
J
2

There is a difference between

class CTestClass
{
public:
    _declspec(dllexport) CTestClass() {}
    _declspec(dllexport) virtual ~CTestClass() {}
};

and

__declspec(dllexport) class CTestClass
{
public:
     CTestClass() {}
     virtual ~CTestClass() {}
};

In the former case you instructed a compiler to export only two member functions: CTestClass::CTestClass() and CTestClass::~CTestClass(). But in the latter case you would instruct a compiler to export the table of virtual functions as well. This table is required once you have got a virtual destructor. So it might be the cause of the crash. When your program tries to call virtual destructor, it looks for it in the associated virtual functions table, but it is not initialized properly so we do not know where it really points then. If your destructor is not virtual, then you do not need any virtual function table and everything works just fine.

Juristic answered 8/11, 2013 at 19:29 Comment(0)
C
0

You didn't really post enough code to be sure. But your example should NOT crash because there isn't anything wrong with it:

int main(int argc, char* argv[])
{
    // 1. Allocated an instance of this class in *this/exe* heap, not the DLL's heap
    // if the constructor allocates memory it will be allocated from the DLL's heap
    CTestClass *foo = new CTestClass;

    // 2. Call the destructor, if it calls delete on anything it will be freed from the DLL's heap since thats where the destructor is executing from. Finally we free the foo object from *this/exe* heap - no problems at all.
    delete foo;
}

I suspect that in your real code you must be using operator delete on an object who's operator new was executed in the context of the dll. And without the virtual keyword you likely miss the destructor call which is doing the cross context delete.

Carnap answered 11/8, 2013 at 22:57 Comment(2)
"your example should NOT crash because there isn't anything wrong with it", this is exactly what i thought too, however i broke the code down to just what was written above and it does in fact fail.Forrest
Can you upload a sample project? There must be something else going wrongCarnap
W
0

virtual destructor is only necessary when you have some inheritance hierarchy tree. Virtual keyword will make sure that pointer to the actual object (not the type of the object) is destroyed by finding its destructor in the Vtable. Since in this example, going by the code you gave, CTestClass is not inheriting from any other class, it is in a way a base class and hence doesn't need a virtual destructor. I'm assuming there maybe another under the hood implementation rule causing this but you shouldn't use virtual with base classes. Anytime you create a derived object, you also create its base (for polymorphic reason) and the base always gets destroyed (the derived only gets destroyed if you make the destructor for it virtual, hence placing it in a runtime vlookup (virtual) table).

Thanks

Whereto answered 10/10, 2013 at 16:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.