It is quite well-known that
// in DLL a
DLLEXPORT MyObject* getObject() { return new MyObject(); }
// in DLL b
MyObject *o = getObject();
delete o;
might certainly lead to crashes.
Whether or not the above has a well-defined characteristic depends on how the MyObject
type is defined.
Iff the class has a virtual destructor, (and that destructor is not defined inline), than it will not crash and will exhibit well-defined behavior.
The reason normally cited for why this crashes is that delete
does two things:
- call destructor
- free memory (by calling
operator delete(void* ...)
)
For a class with a non-virtual destructor, it can do these things "inline", which leads to the situation that delete
inside DLL "b" may try to free memory from the "a" heap == crash.
However, if the destructor of MyObject
is virtual
then before calling the "free" function, the compiler needs to determine the actual run-time class of the pointer before it can pass the correct pointer to operator delete()
:
C++ mandates that you must pass the exact same address to operator
delete as what operator new returns. When you’re allocating an object
using new, the compiler implicitly knows the concrete type of the
object (which is what the compiler uses to pass in the correct memory
size to operator new, for example.)
However, if your class has a base
class with a virtual destructor, and your object is deleted through a
pointer to the base class, the compiler doesn’t know the concrete type
at the call site, and therefore cannot compute the correct address to
pass to operator delete(). Why, you may ask? Because in presence of
multiple inheritance, the base class pointer’s address may be
different to the object’s address in memory.
So, what happens in that
case is that when you delete an object which has a virtual destructor,
the compiler calls what is called a deleting destructor
instead of the usual sequence of a
call to the normal destructor followed by operator delete() to reclaim
the memory.
Since the deleting destructor is a virtual function, at
runtime the implementation of the concrete type will be called, and
that implementation is capable of computing the correct address for
the object in memory. What that implementation does is call the
regular destructor, compute the correct address of the object, and
then call operator delete() on that address.
It seems that both GCC (from the linked article) and MSVC achieve this by calling both the dtor as well as the "free" function from the context of a "deleting destructor". And this helper by necessity lives inside your DLL and will always use the correct heap, even if "a" and "b" have a different one.