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