C++ 在C+中,对象在其生存期内可以合法更改其类型+;?
我有以下代码:C++ 在C+中,对象在其生存期内可以合法更改其类型+;?,c++,visual-c++,polymorphism,rtti,virtual-functions,C++,Visual C++,Polymorphism,Rtti,Virtual Functions,我有以下代码: class Class { public: virtual void first() {}; virtual void second() {}; }; Class* object = new Class(); object->first(); object->second(); delete object; 我用VisualC++ 10用/O2编译,并有这样的分解: 282: Class* object = new Class(); 0040
class Class {
public:
virtual void first() {};
virtual void second() {};
};
Class* object = new Class();
object->first();
object->second();
delete object;
我用VisualC++ 10用/O2编译,并有这样的分解:
282: Class* object = new Class();
00403953 push 4
00403955 call dword ptr [__imp_operator new (4050BCh)]
0040395B add esp,4
0040395E test eax,eax
00403960 je wmain+1Ch (40396Ch)
00403962 mov dword ptr [eax],offset Class::`vftable' (4056A4h)
00403968 mov esi,eax
0040396A jmp wmain+1Eh (40396Eh)
0040396C xor esi,esi
283: object->first();
0040396E mov eax,dword ptr [esi]
00403970 mov edx,dword ptr [eax]
00403972 mov ecx,esi
00403974 call edx
284: object->second();
00403976 mov eax,dword ptr [esi]
00403978 mov edx,dword ptr [eax+4]
0040397B mov ecx,esi
0040397D call edx
285: delete object;
0040397F push esi
00403980 call dword ptr [__imp_operator delete (405138h)]
请注意,在00403968
处,对象起始地址(存储vptr
的位置)被复制到esi
寄存器中。然后在0040396E
处,此地址用于检索vptr
,而vptr
值用于检索first()的地址。然后在00403976
再次检索vptr
,并用于检索second()的地址
为什么要检索两次vptr?对象是否可能在调用之间更改其vptr
,或者它只是一个欠优化
为什么要检索两次vptr?对象是否可能在调用之间更改其vptr,或者它只是一个欠优化
考虑:
object->first();
此调用可能会破坏该对象,并在同一内存块中创建一个新对象。因此,在这次调用之后,无法对状态进行任何假设。例如:
#include <new>
struct Class {
virtual void first();
virtual void second() {}
virtual ~Class() {}
};
struct OtherClass : Class {
void first() {}
void second() {}
};
void Class::first() {
void* p = this;
static_assert(sizeof(Class) == sizeof(OtherClass), "Oops");
this->~Class();
new (p) OtherClass;
}
int main() {
Class* object = new Class();
object->first();
object->second();
delete object;
}
#包括
结构类{
虚空优先();
虚拟void second(){}
虚拟~Class(){}
};
结构OtherClass:类{
void first(){}
void second(){}
};
void类::first(){
void*p=这个;
静态断言(sizeof(Class)=sizeof(OtherClass),“Oops”);
这个->~Class();
新(p)其他类别;
}
int main(){
类*对象=新类();
对象->第一个();
对象->第二个();
删除对象;
}
如果函数是内联的和/或使用链接时代码生成,编译器可以优化不必要的寄存器加载
正如DeadMG和Steve Jessop所指出的,上面的代码表现出未定义的行为。根据C++ 2003标准3.8/7:
如果在对象的生命周期结束后,在重用或释放对象占用的存储之前,在原始对象占用的存储位置创建了一个新对象,一个指向原始对象的指针,一个引用原始对象的引用,或者,原始对象的名称将自动引用新对象,并且在新对象的生存期开始后,可用于操纵新对象,如果:
- 新对象的存储正好覆盖原始对象占用的存储位置,并且
- 新对象与原始对象的类型相同(忽略顶级cv限定符),并且
- 原始对象的类型不是常量限定的,如果是类类型,则不包含任何类型为常量限定的非静态数据成员或引用类型,以及
- 原始对象是T类型的最派生对象(1.8),而新对象是T类型的最派生对象(即,它们不是基类子对象)
上述代码不满足上述列表中的要求2。它存储在esi
中,以便在调用不同函数之间保存
微软大会说
编译器生成prolog和epilog代码,以保存和恢复ESI、EDI、EBX和EBP寄存器(如果在函数中使用)
因此,存储在esi
中的指针将保留,但ecx
中的此
指针可能不会保留。首先回答标题中的问题:
是的,派生类中的对象在构造和销毁期间会更改其类型。这是唯一的情况
问题主体中的代码是不同的。但正如马克西姆正确指出的那样,你只有一个指针。此指针可能(在不同时间)指向位于同一地址的两个不同对象。。我认为这应该是一个无操作。这可能是一个欠优化,如果它查看函数内部,它可能会发现它们都没有改变esi
。好的,对象的开头存储在esi
中,但是为什么vptr
要读取两次?同时保持vptr
还需要一个额外的寄存器,像edi
,然后必须保存和恢复。这会改进代码吗?很难说。@LuchianGrigore:LTCG可能还没有帮助,但它是一种优化,可供未来潜在的编译器使用。@LuchianGrigore:gcc-4.7.1将原始代码优化为发布到调用operator new()
中,然后使用-O3
调用operator delete()
。你是否愿意提供任何支持证据为你的索赔?@ MaximYegorushkin你使用GCC与Visual C++?否则,你就不能回答这个问题。这是关于具有特定设置(-O2)的特定编译器。用Visual C++、-O2和链接时间代码生成产生相同的结果,不是真的。在这个空间中创建除另一个类之外的任何东西都将是一大堆UB。编译器不必关心这个潜在的场景。@DeadMG:在这个空间中创建除另一个类之外的任何东西都将是一大堆UB。嗯,不是真的。你愿意为这一大胆的主张提供任何支持证据吗?