简体   繁体   中英

Why does this static C++ cast work?

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?

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.

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.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM