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オブジェクト start のアドレス (が格納されている場所) がレジスタvptrにコピーされることに注意してください。esi次に、0040396Eこのアドレスで を取得するために使用されvptrvptr値を使用して のアドレスを取得しますfirst()。次に で00403976vptr再度取得され、 のアドレスを取得するために使用されますsecond()

vptr が 2 回取得されるのはなぜですか? 呼び出しの間にオブジェクトがvptr変更された可能性がありますか、それとも最適化が不十分なだけですか?

4

3 に答える 3

9

vptr が 2 回取得されるのはなぜですか? 呼び出しの間にオブジェクトの 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 レジスターが関数で使用されている場合、それらを保存および復元するためのプロローグおよびエピローグ コードを生成します。

そのため、に格納されているポインターesiは残りますが、thisポインターはそうでecxない場合があります。

于 2012-09-18T08:01:47.610 に答える
2

最初にタイトルからの質問に答えるには:

はい、派生クラスからのオブジェクトは、構築および破棄中にその型を変更します。これが唯一のケースです。

質問本文のコードが異なります。しかし、マキシムが正しく指摘しているように、ポインタがあるだけです。このポインターは、同じアドレスに存在する 2 つの異なるオブジェクトを (異なる時点で) 指す場合があります。

于 2012-09-18T09:34:36.120 に答える