The statement:
Base *ptr = new Base;
Doesn't always allocate sizeof(Base) - it would probably allocate more memory. Even if it does allocate exact sizeof(Base) bytes, it doesn't necessarily mean any byte access after this range (i.e. sizeof(Base)+n, n>1) would be invalid.
Hence let's assume the size of class Base is 4 bytes (due to virtual function table in most compiler's implementation, on a 32-bit platform). However, the new operator, the heap-management API, the memory management of OS, and/or the hardware does allocate 16 bytes for this allocation (assumption). This makes additional 12 bytes valid! It makes the following statement valid:
static_ptr->i = 10;
Since now it tries to write 4 bytes (sizeof(int), normally) after the first 4 bytes (size of polymorphic class Base).
The function call:
static_ptr->foo();
would simply make a call to Derived::foo since the pointer is of type Derived, and nothing is wrong in it. The compiler must call Derived::foo. The method Derived::foo doesn't even try to access any data member of derived class (and even base class).
Had you called:
static_ptr->do_derived();
which is accessing i member of derived. It would still be valid, since:
- The function call is always valid, till method tries to access data-member (i.e. accesses something out of
this pointer). - Data-member access became valid due to memory allocation (UD behaviour)
Note that following is perfectly valid:
class Abc { public: void foo() { cout << "Safe"; } }; int main() { Abc* p = NULL; p->foo(); // Safe }
The call it valid, since it translates to:
foo(NULL);
where foo is:
void foo(Abc* p) { // doesn't read anything out of pointer! }
static_cast. See this link.