There is already a question asking about the "real-world" behavior of delete
ing a pointer to a base class that lacks a virtual destructor, but the question is restricted to a very limited case (the derived class has no members with non-trivial destructors), and the accepted answer just says there's no way to know without checking the behavior of every compiler.
....but that isn't actually very helpful; knowing that every compiler might behave differently doesn't tell us anything about the behavior of any particular compiler. So, what do Clang and G++ do in this case? I would assume they would simply call the base-class destructor, then deallocate the memory (for the entire derived class). Is this the case?
Or, if it's not possible to determine this for all versions of GCC and Clang, how about GCC 4.9 and 5.1, and Clang 3.5 through 3.7?
If you delete an object without a virtual destructor, the compiler will probably assume that the deleted address is the address of the most derived object.
Unless you use a primary base class to delete the object, this won't be the case, so the compiler will call
operator delete
with an incorrect address.Of course the compiler will not call the destructor of the derived class, or
operator delete
of the derived class (if there is one).First, the standard disclaimer: this is undefined behavior, so even with one specific compiler, changing the compiler flags, the day of the week, or the way you look at the computer could change the behavior.
The following all assumes you have some sort of at least slightly non-trivial destruction happening in your destructors (e.g., the objects delete some memory, or contain object others that themselves delete some memory).
In the simple case (single inheritance) you typically get something roughly equivalent to static binding--that is, if you destroy a derived object via a pointer to a base object, only the base constructor is invoked so the object isn't destroyed properly.
If you use multiple inheritance, and you destroy an object of derived class via the "first" base class, it'll typically be about the same as if you used single inheritance--the base class destructor will be invoked, but the derived class destructor won't be.
If you have multiple inheritance and destroy a derived object via a pointer to the second (or subsequent) base class, your program will typically crash. With multiple inheritance, you have multiple base class objects at multiple offsets in the derived object.
In the typical case, the first base class will be at the beginning of the derived object, so using the address of derived as a pointer to the first base class object works about the same as in the single inheritance case--we get the equivalent of static binding/static dispatch.
If we try this with any of the other base classes, a pointer to the derived doesn't point to an object of that base class. The pointer needs to be adjusted to point to the second (or subsequent) base class before it can be used as a pointer to that type of object at all.
With a non-virtual destructor, what'll typically happen is that the code will basically take that address of that first base class object, do roughly the equivalent of a
reinterpret_cast
on it, and try to use that memory as if it were an object of the base class specified by the pointer (e.g., base2). For example, let's assume base2 has a pointer at offset 14, and base2's destructor attempts to delete a block of memory it points at. With a non-virtual destructor, it'll probably receive a pointer to the base1 subject--but it'll still look at offset 14 from there, and try to treat that as a pointer, and pass it todelete
. It could be that base1 contains a pointer at that offset, and it's actually pointing at some dynamically allocated memory, in which case this might actually appear to succeed. Then again, it could also be that it's something entirely different, and the program dies with an error message about (for example) attempting to free an invalid pointer.It's also possible that base1 is smaller that 14 bytes in size, so this ends up actually manipulating (say) offset 4 in base2.
Bottom line: for a case like this, things get really ugly in a hurry. The very best you can hope for is that the program dies quickly and loudly.
Just for kicks, quick demo code:
g++/Linux: Segmentation fault
clang/Linux: Segmentation fault
VC++/Windows: Popup: "foo.exe has stopped working" "A problem caused the program to stop working correctly. Please close the program."
If we change the pointer to
base
instead ofbase2
, we get~base
from all the compilers (and if we derive only from one base class, and use a pointer to that base class, we get the same: only that base class' destructor runs).