Revisit Static Cast and Dynamic Cast
From a low level perspective.
1. Static Cast
static_cast
for downcasting(cast Base to Derived) is unsafe and will cause undefined behavior. For example:
1 | class Base { |
The Problem here is the object pointed to by bPtr
is a Base instance, not a Derived instance. Base doesn’t have a y
member, so accessing dPtr->y
is undefined behavior (UB).
From the memory layout perspective, Base
and Derived
have different structures.
1 | Base object memory layout: |
Base* b = new Base{};
setsbPtr
to the address of the Base object, e.g.,0x1000
. The object at0x1000
contains onlyx
.Derived* dPtr = static_cast<Derived*>(bPtr);
tells the compiler to treat the pointerbPtr
as if it points to a Derived object.dPtr
also points to0x1000
- Since
static_cast
is compile-time casting and now the compiler assumesdPtr
points to aDerived
object with layout{x, y}
. - When we do
dPtr->y = 10;
, the compiler knows the layout ofDerived
and calculates the offset ofy
relative to the start of a Derived object. Sincex
is at offset 0 (4 bytes) andy
is at offset 4 (next 4 bytes),dPtr->y
translates to*(dPtr + 4)
(in byte terms).
Writing to 0x1004
(for dPtr->y
) accesses memory beyond the allocated Base object. This is undefined behavior. It is now easy to understand that upcasting using static_cast
is safe since Base
memory layout is part of the Derived
memory layout, and there is no memory overrun issue.
2. Dynamic Cast
dynamic_cast
can also be used for downcasting (Base to Derived). dynamic_cast
relies on Run-Time Type Information (RTTI), which is implemented using vptr
(virtual table pointer) and vtable
in polymorphic classes. In other words, for dynamic_cast
to work, the base class must have at least one virtual function.
1 | class Base { |
What happens internally when we do dynamic_cast<Derived*>(b)
is:
- Since the inheritance is polymorhic, we have a
vptr
pointing to avtable
. - Retrieve
vptr
fromb
and find the correspondingvtable
. - Read RTTI metadata to get type info.
- Check if
b
‘s actual type isDerived
. - Return a valid pointer if the retrived type matches the target cast type.
We can inpsect the raw memory to verify it.
1 |
|
1 | vtable address: 0x5618a6a30d38 |
b
is indeed a Derived
type, so the cast is Ok. From the analysis above, we can conclude that dynamic_cast
only works when there is at least one virtual function in Base
class as it relies on RTTI metadata to decide the runtime type information.
Revisit Static Cast and Dynamic Cast
http://chuzcjoe.github.io/cpp/cpp-revisit-static-cast-dynamic-cast/