9

我有这个代码:

class Class {
public:
    virtual void first() {};
    virtual void second() {};
};

Class* object = new Class();
object->first();
object->second();
delete object;

我用 /O2 用 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这个地址用于检索vptr并且该vptr值用于检索 的地址first()。然后再次检索 at00403976vptr用于检索 的地址second()

为什么 vptr 被检索两次?对象可能vptr在调用之间发生变化,还是只是优化不足?

4

3 回答 3

9

为什么 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;
}

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


正如 DeadMG 和 Steve Jessop 所指出的,上述代码表现出未定义的行为。根据 C++ 2003 标准的 3.8/7:

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

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

上面的代码不满足上面列表中的要求 2。

于 2012-09-18T07:56:38.607 回答
2

它存储在esi不同函数的调用之间以保存。

微软公约说

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

所以存储的指针esi将保留,但this指针ecx可能不会。

于 2012-09-18T08:01:47.610 回答
2

首先回答标题中的问题:

Yes, an object from a derived class changes its type during construction and destruction. This is the only case.

The code in the body of the question is different. But as Maxim correctly notes, you just have a pointer. This pointer may point (at different times) to two different objects residing at the same address.

于 2012-09-18T09:34:36.120 回答