是否允许对象在C ++的生命周期内合法地更改其类型?

时间:2012-09-18 07:48:06

标签: 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;

我使用/ C2使用Visual C ++ 10进行编译并进行反汇编:

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,此地址用于检索vptrvptr值用于检索first()的地址。然后在00403976再次检索vptr并用于检索second()的地址。

为什么vptr检索两次?对象是否可能在调用之间更改vptr,还是仅仅是优化不足?

3 个答案:

答案 0 :(得分:9)

  

为什么vptr检索两次?对象是否可能在调用之间更改其vptr,或者它只是一个欠优化?

考虑:

object->first();

此调用可能会破坏对象并在同一块内存中创建一个新对象。因此,在此调用之后,不能对状态做出任何假设。 E.g:

#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;
}

如果该函数是内联函数和/或使用链接时代码生成,编译器可以优化掉不必要的寄存器加载。


由于DeadMG和Steve Jessop注意到上述代码表现出未定义的行为。根据C ++ 2003标准的3.8 / 7:

  

如果在对象的生命周期结束之后并且在重用或释放对象占用的存储之前,则在原始对象占用的存储位置创建新对象,指向原始对象的指针,引用原始对象的引用,或者原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,就可以用于操作新对象,如果:

     
      
  • 新对象的存储空间正好覆盖原始对象占用的存储位置,
  •   
  • 新对象与原始对象的类型相同(忽略顶级cv限定符),
  •   
  • 原始对象的类型不是const限定的,如果是类类型,则不包含任何类型为const-qualified或引用类型的非静态数据成员,并且
  •   
  • 原始对象是类型为T的派生程度最高的对象(1.8),新对象是类型为T的派生程度最高的对象(即,它们不是基类子对象)。
  •   

上述代码不符合上述清单中的要求2.

答案 1 :(得分:2)

它存储在esi中,以便在对不同功能的调用之间保存。

微软的惯例是

  

编译器生成prolog和epilog代码以保存和恢复ESI,EDI,EBX和EBP寄存器(如果它们在函数中使用)。

因此存储在esi中的指针将保留,但this中的ecx指针可能不会。

答案 2 :(得分:2)

首先回答标题中的问题:

是的,派生类中的对象在构造和销毁期间更改其类型。这是唯一的情况。

问题正文中的代码是不同的。但正如Maxim正确指出的那样,你只需要一个指针。该指针可以指向(在不同时间)驻留在同一地址的两个不同对象。