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。嗯,不是真的。你愿意为这一大胆的主张提供任何支持证据吗?