12

次のコードは 20 を出力します。つまり、sizeof(z) は 20 です。

#include <iostream.h>
class Base
{
      public:
            int a;
};

class X:virtual public Base
{
      public:
            int x;
};

class Y:virtual public Base
{
      public:
            int y;
};

class Z:public X,public Y
{
};

int main()
{
Z z;
cout << sizeof(z) <<endl;
}

ここで仮想基本クラスを使用しない場合、つまり次のコードの場合: sizeof(z) は 16 です。

#include <iostream.h>
class Base
{
      public:
            int a;
};

class X:public Base
{
      public:
            int x;
};

class Y:public Base
{
      public:
            int y;
};

class Z:public X,public Y
{
};

int main()
{
Z z;
cout << sizeof(z) <<endl;
}

最初のケースで sizeof(z) が more(20) なのはなぜですか? Base は Z に 1 回しか含まれないため、12 であるべきではありませんか?

4

3 に答える 3

20

2 つのケースのクラス レイアウトを見てみましょう。

仮想がない場合、それぞれ整数を持つ 2 つの基本クラス (「X」と「Y」) があり、これらの各クラスには、整数を持つ「基本」基本クラスが統合されています。これは、それぞれ 32 ビットの 4 つの整数で、合計 16 バイトです。

Offset  Size  Type  Scope  Name
     0     4   int   Base     a
     4     4   int      X     x
     8     4   int   Base     a
    12     4   int      Y     y
    16 size (Z members would come at the end)

(編集: DJGPP でプログラムを作成してレイアウトを取得し、それを考慮してテーブルを微調整しました。)

それでは、仮想基本クラスについて話しましょう。これらは、クラスの実際のインスタンスを共有インスタンスへのポインターに置き換えます。「Z」クラスには「ベース」クラスが1つしかなく、「X」と「Y」の両方のインスタンスがそれを指しています。したがって、X、Y、および Z には整数がありますが、Z は 1 つしかありません。つまり、整数は 3 つ、つまり 12 バイトです。しかし、X と Y も共有 Z へのポインタを持っています (そうでなければ、どこにあるかわかりません)。32 ビット マシンでは、2 つのポインタによりさらに 8 バイトが追加されます。これで合計 20 が表示されます。メモリ レイアウトは次のようになります (確認していません... ARM には、順序が X、Y、Z、次に Base である例があります)。

Offset  Size        Type  Scope  Name  Value (sort of)
     0     4 Base offset      X     ?  16 (or ptr to vtable)
     4     4         int      X     x
     8     4 Base offset      Y     ?  16 (or ptr to vtable)
    12     4         int      Y     y
    16     4         int   Base     a
    20 size (Z members would come before the Base)

したがって、メモリの違いは、整数が 1 つ少なく、ポインターが 2 つ多いという 2 つの組み合わせです。別の回答とは対照的に、仮想関数がないため、vtables がこれに (編集) 直接 (/編集) ロールを支払うとは思わない。

編集: ppinider は gcc のケースに関する詳細情報を提供しており、gcc が空の vtable (つまり、仮想関数なし) を利用して仮想基底クラスへのポインターを実装していることを示しています。そうすれば、仮想関数があったとしても、クラス インスタンスに追加のポインターを必要とせず、より多くのメモリが必要になります。欠点は、基本クラスに到達するための追加の間接化であると思われます。

すべてのコンパイラがこれを行うことを期待するかもしれませんが、おそらくそうではありません。ARMの225 ページでは、vtable について言及せずに仮想基本クラスについて説明しています。ページ 235 は、具体的には「仮想関数を持つ仮想基本クラス」に対応しており、vtable へのポインターとは別の X および Y 部分からのポインターがあるメモリ レイアウトを示す図があります。Base へのポインターがテーブルの観点から実装されることを当然のことと考えないことをお勧めします。

于 2008-12-28T16:24:33.540 に答える
9

Mark Santesson の答えは大筋ではありますが、vtable が存在しないという主張は正しくありません。g++ -fdump-class-hierarchy を使用して、何が起こっているかを表示できます。これは、仮想環境がない場合です。

Class Base
   size=4 align=4
   base size=4 base align=4
Base (0x19a8400) 0

Class X
   size=8 align=4
   base size=8 base align=4
X (0x19a8440) 0
  Base (0x19a8480) 0

Class Y
   size=8 align=4
   base size=8 base align=4
Y (0x19a84c0) 0
  Base (0x19a8500) 0

Class Z
   size=16 align=4
   base size=16 base align=4
Z (0x19b1800) 0
  X (0x19a8540) 0
    Base (0x19a8580) 0
  Y (0x19a85c0) 8
    Base (0x19a8600) 8

「基本サイズ」引数に特に注意してください。今度は仮想ケースで、Z のみを表示します。

Class Z
   size=20 align=4
   base size=16 base align=4
Z (0x19b3000) 0
    vptridx=0u vptr=((& Z::_ZTV1Z) + 12u)
  X (0x19a8840) 0
      primary-for Z (0x19b3000)
      subvttidx=4u
    Base (0x19a8880) 16 virtual
        vbaseoffset=-0x0000000000000000c
  Y (0x19a88c0) 8
      subvttidx=8u vptridx=12u vptr=((& Z::_ZTV1Z) + 24u)
    Base (0x19a8880) alternative-path

「基本サイズ」は同じですが、「サイズ」は 1 つ多くのポインターであり、vtable ポインターがあることに注意してください。これには、次に説明するように、親クラスの構築 vtable とすべてのクラス間マジック (構築 vtable、および仮想テーブル テーブル (VTT)) が含まれます。

http://www.cse.wustl.edu/~mdeters/seminar/fall2005/mi.html

実際の関数ディスパッチ vtable は空になることに注意してください。

于 2008-12-28T18:24:54.577 に答える
3

余分なサイズはおそらく、仮想クラスと多重継承によって割り当てられた余分な VTable ( http://en.wikipedia.org/wiki/Vtable ) が原因です。

于 2008-12-28T16:01:43.620 に答える