C++ 为什么这个静态C++;演员工作?

C++ 为什么这个静态C++;演员工作?,c++,casting,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 * stat

想象一下这个代码:

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;
}
类基{
公众:
虚拟void foo(){}
};
派生类:公共基{
公众:
int i;
void foo()重写{}
void do_派生(){
std::cout i=10;//为什么这样做?
static_ptr->foo();//为什么这样做?
返回0;
}
为什么我会在控制台上得到结果10?我想知道,因为我认为ptr是指向基本对象的指针。因此该对象不包含int I或方法
do_-derived()
。是否自动生成新的派生对象

当我也在基类中声明一个虚拟的
do_-derived()
方法时,就会选择这个方法,但是为什么呢?

int*I=newint{1};
int* i = new int{1};
delete i;
std::cout << *i << std::endl;
删除i;
std::cout如评论中所述,“碰巧做了你所期望的”与“工作”不同

让我们做一些修改:

#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;
}
在本例中,所有操作都没有达到我们预期的效果。分配到数组中会导致segfault

为什么这个静态演员工作

因为静态强制转换是编译时检查器。基类和派生类之间存在关系。因为它有关系,所以静态强制转换相信是这种关系,相信也是程序员。所以作为程序员,您应该确保基类对象不应静态强制转换为派生类对象。

语句:

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

因此,让我们假设类库的大小为4字节(由于大多数编译器在32位平台上实现虚拟函数表)。但是,
new
运算符、堆管理API、操作系统内存管理和/或硬件确实为此分配分配了16字节(假设)。这使附加的
12
字节有效!它使以下语句有效:

static_ptr->i = 10;
因为现在它尝试在前4个字节(多态类的大小
Base
)之后写入4个字节(
sizeof(int)

函数调用:

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

你打过电话吗

static_ptr->do_derived();
它正在访问派生的
i
成员。它仍然有效,因为:

  • 函数调用始终有效,直到方法尝试访问数据成员(即从
    指针中访问某些内容)
  • 由于内存分配(UD),数据成员访问变得有效 (行为)
请注意,以下内容完全有效:

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

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

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

它不起作用。它是未定义的行为。未定义的行为有时可能会起作用,但这只是一种让你产生虚假安全感的策略。它在等待时机,并将在最糟糕的时刻开始持续不断地失败。@πάνταῥεῖ 我认为他实际上是不走运的:)这种无声的错误(或UB)可能是地狱般的调试。@毫无用处,你的意思肯定是“在最糟糕的时刻不一致地、不可恢复地失败”在编译代码和编译代码时,区分代码是非常重要的。编译的代码不一定是正确的。C++标准规定了编译器不需要检测的许多限制,例如使用<代码> StasyType Case< /Code >。强制转换为派生类对象。
反对!如果此类强制转换的源始终真正是该派生类型(或其子类),那么这样的
static\u cast
是完全有效的。我不相信在cast总是有效的情况下总是
dynamic\u cast
ing;这是为你不用的东西付费。我认为你的
p->foo()
示例是不安全的,即使它不从指针中读取任何内容。如本答案中所述,
p->foo()
相当于
(*p).foo()
,它取消了对空指针的引用。编译器会将两者转换为
foo(p)
其中
p
将是
foo
@EldritchCheese中的
这个
指针,然而,我确实同意它实际上是UD。然而,我仍然说,如果
p->f()
是有效的,那么在某些编译器上
(*p).f()
将是有效的。
    foo(NULL);
void foo(Abc* p)
{
    // doesn't read anything out of pointer!
}