It is undefined behavior to delete an array of B
as an array of A
. This is true so long as A
has a non-trivial destructor (when it does have a trivial destructor, it may be defined, I do not recall). Inheritance, virtual -- either matter to its undefinedness in this case.
Everything after that is better than your harddrive's image files and browser password cache being emailed to your contact list, which is a legal example of "undefined behavior" under the C++ standard. You got lucky.
In particular, I'd guess that A
is a trivial object. and delete[]
is expecting to delete a series of size-1 trivial objects. Somehow the fact that B
is much larger and non-trivial (it contains a vtable for one) has messed your compiler up and led to your infinite loop.
Maybe the format it stores the information on how to delete an array is different with trivial objects with non-virtual destructors. With the vtable case, maybe it stores a function pointer there, and in the trivial case it stores a count.
The loop then isn't infinite, but rather iterates (nearly random 32 or 64 bit number) times, as pointer values tend to be relatively random.
If you really care what specific undefined behavior your code resulted in on this particular version of this particular compiler on this particular system with this particular context, you can godbot your code. But I don't see why you should care.
C++ vtables are only created for a type if needed for that type. Inheriting and adding virtual
afterwards doesn't change the fact the type does, or does not, need a vtable.
Raw arrays in C are not contravariant nor covariant. If you have an array, you cannot safely convert it to a pointer to any different type (there are some extremely narrow exceptions involving standard layout and raw bytes/chars and the like).
If we go back to your example and remove the array:
A * a = new B;
delete a;
this gets less bad. The delete a remains UB, as you are deleting a B
as an A
.
Without virtual ~A()
in A
, you cannot delete a B
as an A
.
To fix this we add:
virtual ~A() { cout << "by" << endl; }
and now A
has a vtable -- instance of A
carry a pointer to a vtable, which (among other things) tells the compiler how to delete an A
or a derived A
type.
Now the code is well defined, and it prints
hey
from b
by
if my head-compiler got it right.