为什么这个静态C ++转换工作?

时间:2017-05-19 11:43:52

标签: c++ casting

想象一下这段代码:

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

为什么我在控制台上获得结果10?我想知道因为我认为ptr是指向基础对象的指针。因此,该对象不包含int i或方法do_derived()。是否自动生成了一个新的派生对象?

当我在Base类中声明一个虚拟do_derived()方法时,会选择这个方法,但为什么呢?

4 个答案:

答案 0 :(得分:26)

int* i = new int{1};
delete i;
std::cout << *i << std::endl;

如果工作的定义是代码将编译并执行,那么这也将“起作用”。

然而,这显然是未定义的行为,并且无法保证可能发生的事情。

在您的情况下,代码编译为static_cast将不执行任何检查,它只是转换指针。访问尚未分配和初始化的内存仍然是未定义的行为。

答案 1 :(得分:8)

正如评论中所提到的,“恰好按照预期行事”与“作品”不同。

让我们做一些修改:

#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;
}

示例输出:

Base::foo

Process finished with exit code 11

在这种情况下,没有一项行动符合我们的预期。分配到数组导致了段错误。

答案 2 :(得分:4)

声明:

Base *ptr = new Base;

并不总是分配sizeof(Base) - 它可能会分配更多内存。即使它确实分配了精确的sizeof(Base)字节,也不一定意味着在此范围之后的任何字节访问(即sizeof(Base)+n,n> 1)都将无效。

因此我们假设类Base的大小是4个字节(由于大多数编译器实现中的虚函数表,在32位平台上)。但是,new运算符,堆管理API,OS的内存管理和/或硬件确实为此分配分配了16个字节(假设)。这使得额外的12字节有效!它使以下声明有效:

static_ptr->i = 10;

从现在开始尝试在前4个字节(多态类sizeof(int)的大小)之后写入4个字节(通常为Base)。

函数调用:

static_ptr->foo();

只会调用Derived::foo,因为指针的类型为Derived,并且没有任何错误。编译器必须调用Derived::foo。方法Derived::foo甚至不尝试访问派生类(甚至基类)的任何数据成员。

你打过电话:

static_ptr->do_derived();

正在访问派生的i成员。它仍然有效,因为:

  • 函数调用始终有效,直到方法尝试访问数据成员(即从this指针访问某些内容)。
  • 由于内存分配(UD,数据成员访问权限)变为有效 行为)

请注意,以下内容完全有效:

class Abc
{
public:
void foo() { cout << "Safe"; }
};

int main()
{
   Abc* p = NULL;
   p->foo(); // Safe
}

调用有效,因为它转换为:

    foo(NULL);

其中foo是:

void foo(Abc* p)
{
    // doesn't read anything out of pointer!
}

答案 3 :(得分:0)

  

为什么这个静态演员有效?

因为静态强制转换是编译时检查器。 Base和Derived之间存在关系。由于它有关系,静态演员相信这种关系并且相信程序员也是如此。因此,作为程序员,您应确保不应将Base对象静态转换为派生类对象。