I think the class names in your example are a bit confusing. Let's call them Interface, Base and Impl. Note that Interface and Base are unrelated.
The C++ Standard defines the C-style cast, called "explicit type conversion (cast notation)" in [expr.cast]. You can (and maybe should) read that whole paragraph to know exactly how the C-style cast is defined. For the example in the OP, the following is sufficient:
A C-style can performs a conversion of one of [expr.cast]/4:
- const_cast
- static_cast
- static_castfollowed by- const_cast
- reinterpret_cast
- reinterpret_castfollowed by- const_cast
The order of this list is important, because:
If a conversion can be interpreted in more than one of the ways listed above, the interpretation that appears first in the list is used, even if a cast resulting from that interpretation is ill-formed.
Let's examine your example
Impl impl;
Interface* pIntfc = &impl;
Base* pBase = (Base*)pIntfc;
A const_cast cannot be used, the next element in the list is a static_cast. But the classes Interface and Base are unrelated, therefore there is no static_cast that can convert from Interface* to Base*. Therefore, a reinterpret_cast is used.
Additional note: the actual answer to your question is: as there is no dynamic_cast in the list above, a C-style cast never behaves like a dynamic_cast.
How the actual address changes is not part of the definition of the C++ language, but we can make an example of how it could be implemented:
Each object of a class with at least one virtual function (inherited or own) contains (read: could contain, in this example) a pointer to a vtable. If it inherits virtual functions from multiple classes, it contains multiple pointers to vtables. Because of empty base class optimization (no data members), an instance of Impl could look like this:
+=Impl=======================================+
|                                            |
|  +-Base---------+   +-Interface---------+  |
|  | vtable_Base* |   | vtable_Interface* |  |
|  +--------------+   +-------------------+  |
|                                            |
+============================================+
Now, the example:
     Impl  impl;
     Impl* pImpl  = &impl;
Interface* pIntfc = pImpl;
     Base* pBase  = pImpl;
+=Impl=======================================+
|                                            |
|  +-Base---------+   +-Interface---------+  |
|  | vtable_Base* |   | vtable_Interface* |  |
|  +--------------+   +-------------------+  |
|  ^                  ^                      |
+==|==================|======================+
^  |                  |
|  +-- pBase          +-- pIntfc
|
+-- pimpl
If you instead do a reinterpret_cast, the result is implementation-defined, but it could result in something like this:
     Impl  impl;
     Impl* pImpl  = &impl;
Interface* pIntfc = pImpl;
     Base* pBase  = reinterpret_cast<Base*>(pIntfc);
+=Impl=======================================+
|                                            |
|  +-Base---------+   +-Interface---------+  |
|  | vtable_Base* |   | vtable_Interface* |  |
|  +--------------+   +-------------------+  |
|                     ^                      |
+=====================|======================+
^                     |
|                     +-- pIntfc
|                     |
+-- pimpl             +-- pBase
I.e. the address is unchanged, pBase points to the Interface subobject of the Impl object.
Note that dereferencing the pointer pBase takes us to UB-land already, the Standard doesn't specify what should happen. In this exemplary implementation, if you call pBase->GetType(), the vtable_Interface* is used, which contains the SomeMethod entry, and that function is called. This function doesn't return anything, so in this example, nasal demons are summoned and take over the world. Or some value is taken from the stack as a return value.