我有以下代码:

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

为什么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 Standard的3.8/7:



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

10-07 19:11