It's because undefined behavior can have unexpected results, including appearing to work.
Anything can happen, this is undefined behavior. What probably actually does happen is that B::c()
uses the location where B::x
would have been stored in a B
instance, right after1 the A
subobject. And it overwrites memory belonging to some other object. In your case, the value was previously 0
and became 1
, but it could have been whatever value was put there by the real owner of that location.
(1) If the compiler implements the empty base class optimization, B::x
and the B::A
subobject could actually overlap.
If you're asking why the compiler doesn't prevent it, it's because you've overridden type checking by using a static_cast
. Of all the casts, only dynamic_cast
pays any attention to the true type of the object, and even that is easy to break.
You're composing two operations which are well-defined in a very unsafe way.
- The compiler can't stop you from casting an
A*
to aB*
, even when it knows there is noB
object, because thatB*
is perfectly useful for the solitary purpose of casting back again. - And it can't stop you from using a
B*
to accessB
members.
The problem is that you used this "fake" B*
for something other than casting back to its true type.