10

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?

23
  • 33
    It doesn't work. It is undefined behavior. Commented May 19, 2017 at 11:44
  • 17
    Undefined Behaviour may sometimes appear to work, but this is just a ploy to lull you into a false sense of security. It's biding its time, and will start consistently and reproducibly failing at the worst possible moment. Commented May 19, 2017 at 11:47
  • 17
    @πάνταῥεῖ I think he's in fact unlucky :) This kind of silent error (or UB) can be hell to debug. Commented May 19, 2017 at 11:48
  • 10
    @Useless surely you mean "inconsistently and irreproducibly failing at the worst possible moment" Commented May 19, 2017 at 11:57
  • 3
    @mcAngular2 It's very important to distinguish between code that compiles and code that is correct. Code that compiles is not necessarily correct. The c++ standard specifies many restrictions that compilers aren't required to detect, such as your use of static_cast. See this link. Commented May 19, 2017 at 12:06

4 Answers 4

25
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.

Sign up to request clarification or add additional context in comments.

Comments

8

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.

Comments

3

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! } 

3 Comments

I think that your p->foo() example is not safe, even though it does not read anything out of the pointer. As mentioned in this answer stackoverflow.com/a/2474021/2689797, p->foo() is equivalent to (*p).foo(), which dereferences the null pointer.
Compiler would translate both of them as foo(p) where p will be the this pointer in foo.
@EldritchCheese, however, I do agree what it is in fact UD. However, I still say that if p->f() is valid, on some compiler then (*p).f() would be valid.
0

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.

1 Comment

you should make sure that Base object should not be static casted to derived class object. Objection! If the source of such a cast will always genuinely be that derived type (or a subclass of it), then such a static_cast is perfectly valid. I don't believe in always dynamic_casting in cases where the cast is always going to be valid; that's paying for what you don't use.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.