CPP Object Model: Data Member Layout
目录

1. 钻石继承

如果class是没有data member的empty class:

class A{};

那么sizeof(A)的结果实际上是1,被编译器安插1byte用于区分该class的两个不同objects。

把A视作empty virtual base class从而进行virtual继承的classes:

class B: public virtual A {};
class C: pbulic virtual A {};

它们的sizeof(B)sizeof(C)在现代GCC和VC上都是pointer的长度。 这是因为它们的object有一个:

  • 指向virtual base class subobject的pointer;或者
  • 指向vtbl的vptr(GCC, 可能有多个virtual base classes?)

进而一个钻石型继承:

//          A
//        /   \
//       B     C
//        \   /
//          D
//
class D: public B, public C {};

D的大小sizeof(D)在GCC上为2个pointers的长度(详解见下一节)。

以上这些都是由各个编译器自己决定的,C++标准未规定。

2. Data Member Layout

同一access section(private/public/protected)中的nonstatic data members在class object中的排序顺序将和声明顺序相同。 但不同的两个private访问域的排列顺序理论上可以不同。 目前编译器都是按照data members在class中声明顺序排列。

object的大小由3个部分组成:

  1. data members大小
  2. alignment
  3. virtual机制隐藏的vptr(可在object开始(GCC, 丧失C语言兼容性)或结束之处(性能损失))

object在继承非多态情况下,object大小由前两部分依序组成。这样也便于切割。

object在多态情况下的大小:

  • 单一具体继承:base class object仍将占有它们原先的大小(包括上面三部分!)。 如果之前base class object已经有vptr,后续的derived class object就无需添加新的vptr。这种情况下只有一个vptr。

  • 多重具体继承:对于每个无继承关系的base class object仍将占有它们原先大小,包括各自的vptr。对于多重继承前述base classes的derived class,其保留每个base class objects的vptrs,但也不添加新的vptr。如果所有base class objects都没有vptrs,而当前derived class object有虚函数,则添加一个vptr。

  • 虚拟继承:在virtual function table中插入一项,放置virtual base class subobject相对当前derived object起始位置的offset。因此如果object存在vptr,那么object大小不会变。如果不存在vptr,则添加一个vptr至object并在vtbl中插入一项存放virtual base class subobject的offset。(GCC方法)

data members的地址获取:

  • static data members: 其地址可以在编译时就获得。需要name-mangling。
  • nonstatic data members: 其offset编译时可获得,运行时利用object地址+offset获得data member地址。值类型的object甚至编译时就可以获得data member地址(编译器支持),pointer/reference必须运行时获得。virtual base class subobject更间接一层获得data member地址。

3. 测试

#include <stdio.h>
using namespace std;

class X {};
class Y {};
class Z1: public virtual X {};
class Z2: public virtual X, public virtual Y {};
class Z3: public virtual X, public virtual Y{virtual void foo(){int a;a++;}};
class A: public Z1, public Z2{};

int main(int argc, char *argv[])
{
Z1 z11;
Z1 z12;
Z2 z21;
Z2 z22;
Z3 z31;
Z3 z32;

printf("%d, %x\n", sizeof(z11), *(long*)(&z11));    // 8, 400c08   虚继承,vptr指向vtbl,vtbl中存放X object偏移,vtbl共享
printf("%d, %x\n", sizeof(z12), *(long*)(&z12));    // 8, 400c08   虚继承,vptr指向vtbl,vtbl中存放X object偏移,vtbl共享

printf("%d, %x\n", sizeof(z21), *(long*)(&z21));    // 8, 400be0   多重虚继承,vptr指向vtbl,vtbl中存放X,Y object偏移,vtbl共享
printf("%d, %x\n", sizeof(z22), *(long*)(&z22));    // 8, 400be0   多重虚继承,vptr指向vtbl,vtbl中存放X,Y object偏移,vtbl共享

printf("%d, %x\n", sizeof(z31), *(long*)(&z31));    // 8, 400ba0   多重虚继承,vptr指向vtbl,vtbl中存放X,Y object偏移和vfunc,vtbl共享
printf("%d, %x\n", sizeof(z32), *(long*)(&z32));    // 8, 400ba0   多重虚继承,vptr指向vtbl,vtbl中存放X,Y object偏移和vfunc,vtbl共享

A a;
printf("%d, %x\n", sizeof(a), *(long*)(&a));        // 16, 400b00  多重继承,每个base class都有vptr
printf("%d, %x\n", sizeof(a), *((long*)(&a) + 1));  // 16, 400b20

return 0;
}
#include <stdio.h>

class A {virtual void foo(){int a;a++;}};
class B {virtual void foo(){int b;b++;}};
class C: public A, public B {};
class D: public A {virtual void fkkc(){int d;d++;}};
class E: virtual public A {virtual void fkkc(){int d;d++;}};

int main(int argc, char *argv[])
{
A a1;
A a2;
B b;
C c;
D d;
E e;


printf("%d, %x\n", sizeof(a1), *(long*)(&a1));  // 8, 400b80  存在vptr
printf("%d, %x\n", sizeof(a2), *(long*)(&a2));  // 8, 400b80  存在vptr, vtbl共享
printf("%d, %x\n", sizeof(b), *(long*)(&b));    // 8, 400b60  存在vptr
printf("%d, %x\n", sizeof(c), *(long*)(&c));    // 16, 400b30 多重继承,每个base class有自己vptr
printf("%d, %x\n", sizeof(d), *(long*)(&d));    // 8, 400b10  单一继承,派生类使用基类vptr
printf("%d, %x\n", sizeof(e), *(long*)(&e));    // 8, 400ae0  单一虚继承,vtbl中存在虚基类偏移和vfunc(可能)

return 0;
}

发表评论