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) ?不应该是 12,因为 Base 在 Z 中只包含一次?

4

3 回答 3

20

我们来看看这两种情况的类布局。

没有虚拟,你有两个基类(“X”和“Y”),每个基类都有一个整数,每个类都集成了一个“基”基类,它也有一个整数。那是 4 个整数,每个 32 位,总共 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”类只有一个“Base”类,“X”和“Y”的两个实例都指向它。因此,您在 X、Y 和 Z 中有整数,但只有一个 Z。这意味着您有三个整数,即 12 个字节。但是 X 和 Y 也有一个指向共享 Z 的指针(否则他们不知道在哪里找到它)。在 32 位机器上,两个指针将额外增加 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)

所以内存差异是两件事的结合:少一个整数和多两个指针。与另一个答案相反,我不相信 vtables 支付任何(编辑)直接(/编辑)滚动,因为没有虚拟功能。

编辑:ppinsider 提供了有关 gcc 案例的更多信息,其中他演示了 gcc 通过使用否则为空的 vtable(即,没有虚拟函数)来实现指向虚拟基类的指针。这样,如果有虚函数,它就不需要类实例中的额外指针,需要更多内存。我怀疑缺点是获得基类的额外间接。

我们可能期望所有编译器都这样做,但也许不是。ARM第225 页讨论了虚拟基类,但未提及 vtable。第 235 页专门针对“具有虚拟函数的虚拟基类”,并有一个图表指示内存布局,其中 X 和 Y 部分的指针与指向 vtable 的指针是分开的。我建议任何人不要想当然地认为指向 Base 的指针将以表格的形式实现。

于 2008-12-28T16:24:33.540 回答
9

Mark Santesson 的回答几乎是在金钱上,但没有 vtables 的断言是不正确的。您可以使用 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

注意“base size”是一样的,但是“size”多了一个指针,注意现在多了一个vtable指针!这又包含父类的构造 vtable,以及所有类间魔法(构造 vtable 和虚拟表表 (VTT)),如下所述:

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

请注意,实际的函数调度 vtable 将为空。

于 2008-12-28T18:24:54.577 回答
3

额外的大小可能是因为虚拟类和多重继承分配了额外的 VTables ( http://en.wikipedia.org/wiki/Vtable )。

于 2008-12-28T16:01:43.620 回答