想象一下这段代码:
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()
方法时,会选择这个方法,但为什么呢?
答案 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
指针访问某些内容)。请注意,以下内容完全有效:
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对象静态转换为派生类对象。