Reputation: 309
Imagine this code:
class Base {
public:
virtual void foo(){}
};
class Derived: public Base {
public:
int i;
void foo() override {}
void do_derived() {
std::cout << i;
}
};
int main(){
Base *ptr = new Base;
Derived * static_ptr = static_cast<Derived*>(ptr);
static_ptr->i = 10; // Why does this work?
static_ptr->foo(); // Why does this work?
return 0;
}
Why do I get the result 10 on the console? I wonder because I thought the ptr is a pointer to a base object. Therefore the object doesn't contain a int i or the method do_derived()
. Is a new derived-Object automatically generated?
When I declare a virtual do_derived()
method in the Base class too, then this one is chosen, but why?
Upvotes: 10
Views: 2247
Reputation: 18411
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:
this
pointer).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!
}
Upvotes: 3
Reputation: 69882
As mentioned in the comments, "happens to do what you expected" is not the same as "works".
Let's make a few modifications:
#include <iostream>
#include <string>
class Base{
public:
virtual void foo(){
std::cout << "Base::foo" << std::endl;
}
};
class Derived: public Base{
public:
int a_chunk_of_other_stuff[1000000] = { 0 };
std::string s = "a very long string so that we can be sure we have defeated SSO and allocated some memory";
void foo() override {
std::cout << "Derived::foo" << std::endl;
}
void do_derived() {
std::cout << s << std::endl;
}
};
int main(){
Base *ptr = new Base;
Derived * static_ptr = static_cast<Derived*>(ptr);
static_ptr -> foo(); // does it though?
static_ptr -> do_derived(); // doesn't work?
static_ptr->a_chunk_of_other_stuff[500000] = 10; // BOOM!
return 0;
}
Sample Output:
Base::foo
Process finished with exit code 11
In this case, none of the operations did what we expected. The assignment into the array caused a segfault.
Upvotes: 8
Reputation: 15524
int* i = new int{1};
delete i;
std::cout << *i << std::endl;
This will also "work", if the definition of working is that the code will compile and execute.
However, it is clearly undefined behavior and there are no guarantees as to what might happen.
In your case, the code compiles as static_cast
won't perform any checks, it just converts the pointer. It is still undefined behavior to access memory that hasn't been allocated and initialized though.
Upvotes: 25
Reputation: 1843
why does this static cast work?
Because static cast is compile time checker. There is a relationship between Base and Derived. Since it has relationship, static cast believe's that relationship and believe's the programmer too. So As a programmer, you should make sure that Base object should not be static casted to derived class object.
Upvotes: 0