10

私はC++で仮想継承を「遊んで」おり、クラスオブジェクトがどのように配置されているか知りたいです。私はそれらの3つのクラスを持っています:

class A {
private:
    int a;
public:
    A() {this->a = 47;}
    virtual void setInt(int x) {this->a = x;}
    virtual int getInt() {return this->a;}
    ~A() {this->a = 0;}
};

class B {
private:
    int b;
public:
    B() {b = 48;}
    virtual void setInt(int x) {this->b = x;}
    virtual int getInt() {return this->b;}
    ~B() {b = 0;}
};

class C : public A, public B {
private:
    int c;
public:
    C() {c = 49;}
    virtual void setInt(int x) {this->c = x;}
    virtual int getInt() {return this->c;}
    ~C() {c = 0;}
};

(私はそれらが正しいと思います:p)

-fdump-class-hierarchyはg++で使用しました、そして私はこれを手に入れました

Vtable for A
A::_ZTV1A: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI1A)
16    A::setInt
24    A::getInt

Class A
   size=16 align=8
   base size=12 base align=8
A (0x10209fb60) 0
    vptr=((& A::_ZTV1A) + 16u)

Vtable for B
B::_ZTV1B: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI1B)
16    B::setInt
24    B::getInt

Class B
   size=16 align=8
   base size=12 base align=8
B (0x1020eb230) 0
    vptr=((& B::_ZTV1B) + 16u)

Vtable for C
C::_ZTV1C: 8u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI1C)
16    C::setInt
24    C::getInt
32    (int (*)(...))-0x00000000000000010
40    (int (*)(...))(& _ZTI1C)
48    C::_ZThn16_N1C6setIntEi
56    C::_ZThn16_N1C6getIntEv

Class C
   size=32 align=8
   base size=32 base align=8
C (0x1020f5080) 0
    vptr=((& C::_ZTV1C) + 16u)
  A (0x1020ebd90) 0
      primary-for C (0x1020f5080)
  B (0x1020ebe00) 16
      vptr=((& C::_ZTV1C) + 48u)

(int (*)(...))-0x00000000000000010さて、それらは一体何C::_ZThn16_N1C6setIntEi and (int (*)(...))0ですか?誰かがダンプを説明できますか?

ありがとうございました。

4

2 に答える 2

6

この答えが正しいかどうかは100%わかりませんが、これが私の最善の推測です。

乗算と非仮想を継承するクラスがある場合、クラスのレイアウトは通常、最初の基本タイプの完全なオブジェクト、次に2番目の基本タイプの完全なオブジェクト、そしてオブジェクト自体のデータです。Bを見ると、Aオブジェクトのvtableポインターが表示され、Cを見ると、AオブジェクトとBオブジェクトの両方のvtableへのポインターがあることがわかります。

オブジェクトはこのように配置されているため、オブジェクトをB*指すポインタがあるC場合、そのポインタは実際にはオブジェクトのベースにありません。むしろそれは真ん中のどこかを指しているでしょう。つまり、オブジェクトをにキャストする必要がある場合は、ポインタをある程度A*調整して、オブジェクトの先頭にスキップして戻す必要があります。B*これを行うには、コンパイラは、オブジェクトの先頭に到達するためにスキップして戻す必要のあるバイト数をどこかにエンコードする必要があります。最初(int(*)(...))は、実際には、オブジェクトの先頭に到達するために確認する必要のある生のバイト数だと思います。お気づきの方もいらっしゃると思いますが、Avtableの場合、このポインタは0です(Aのvtableはオブジェクトの先頭にあり、同じことがオブジェクトの先頭にあるためです。Bvtable(オブジェクトの先頭にも存在するため。ただし、Cvtableには2つの部分があることに注意してください。最初の部分はのvtableでAあり、最初のクレイジーエントリもゼロです(Avtableにいる場合は、調整を行う必要はありません)。ただし、このテーブルの前半がBvtableのように見えるので、最初のエントリが16進値であることに注意してください。オブジェクトのレイアウト-0x10を見ると、次のことがわかります。 vtableポインターCBvtableポインターの16バイト後Aです。この値は、オブジェクトのルートに戻るためにvtableポインターを-0x10スキップして戻る必要がある修正オフセットである可能性があります。B

各vtableの2番目のクレイジーなエントリは、vtable自体へのポインタのようです。これは常にvtableオブジェクトのアドレスと等しいことに注意してください(vtableの名前とそれが指しているものを比較してください)。これは、実行時型の識別を行う場合に必要になります。これは、通常、vtableのアドレス(または少なくともその前面近くの何か)を調べる必要があるためです。

C最後に、 vtableの最後に不可解な名前のsetInt関数とgetInt関数がある理由については、型がandという名前の2つCの異なる関数のセットを継承しているためだと確信しています。推測しなければならない場合、ここでのマングリングは、コンパイラの内部が2つの仮想関数を区別できるようにすることです。setIntgetIntAB

お役に立てれば!

于 2011-01-05T22:16:51.960 に答える
6

これがc++filtを介して実行されたダンプです:

Vtable for A
A::vtable for A: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& typeinfo for A)
16    A::setInt
24    A::getInt

Class A
   size=16 align=8
   base size=12 base align=8
A (0x10209fb60) 0
    vptr=((& A::vtable for A) + 16u)

Vtable for B
B::vtable for B: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& typeinfo for B)
16    B::setInt
24    B::getInt

Class B
   size=16 align=8
   base size=12 base align=8
B (0x1020eb230) 0
    vptr=((& B::vtable for B) + 16u)

Vtable for C
C::vtable for C: 8u entries
0     (int (*)(...))0
8     (int (*)(...))(& typeinfo for C)
16    C::setInt
24    C::getInt
32    (int (*)(...))-0x00000000000000010
40    (int (*)(...))(& typeinfo for C)
48    C::non-virtual thunk to C::setInt(int)
56    C::non-virtual thunk to C::getInt()

Class C
   size=32 align=8
   base size=32 base align=8
C (0x1020f5080) 0
    vptr=((& C::vtable for C) + 16u)
  A (0x1020ebd90) 0
      primary-for C (0x1020f5080)
  B (0x1020ebe00) 16
      vptr=((& C::vtable for C) + 48u)

(int (*)(...))-0x00000000000000010とが何であるかわかりません(int (*)(...))0
この部分は、ここでC::_ZThn16_N1C6setIntEi/C::non-virtual thunk to C::setInt(int)説明する「複数または仮想継承が存在する場合の仮想関数呼び出しの最適化」です。

于 2011-01-05T22:37:27.090 に答える