注意:以下分析均基于vs2017,不同编译器在多继承时布局可能有差异,但占用字节数一般不变
给出如下的类定义:
class A
{
};
class B
{
int bi;
virtual void func0() { }
};
class C
{
char c;
int ci;
virtual void func() { }
virtual void func1() { }
};
class D : public A, public C
{
int di;
virtual void func() { }
virtual void func1() { }
};
class E : public B, public C
{
int ei;
virtual void func0() { }
virtual void func1() { }
virtual void func2() { }
};
来分析五个类的内存布局:
A不说,空类,占用1个字节
B,首先是一个虚函数表指针,占用4字节,之后一个int类型的成员变量,4字节
C,同样一个虚函数表指针,4字节(0~3);char类型的字符c,1字节,之后是整型变量ci,但由于 字节对齐的存在,会自动给char c填充三个字节,是的c占用4 ~ 7这四个字节,ci占用8~11,共12字节
这三个基类的内存布局如下:
1>class A size(1):
1> +---
1> +---
1>
1>class B size(8):
1> +---
1> 0 | {vfptr}
1> 4 | bi
1> +---
1>
1>B::$vftable@:
1> | &B_meta
1> | 0
1> 0 | &B::func0
1>
1>B::func0 this adjustor: 0
1>
1>class C size(12):
1> +---
1> 0 | {vfptr}
1> 4 | c
1> | <alignment member> (size=3)
1> 8 | ci
1> +---
1>
1>C::$vftable@:
1> | &C_meta
1> | 0
1> 0 | &C::func
1> 1 | &C::func1
此后是两个派生类D和E
D首先继承了A和C,因此保留A和C的成员(此时由于A无成员,因此此时占用0字节)
因此D的前12字节为C的成员,第12个字节开始,为D的成员变量di,占用4字节,共15字节
且观察下面的内存布局,由于D覆盖了C的虚函数,因此D的虚函数表中均为自身派生类
而对于E,前面都是一样的,前8字节为基类B,之后12字节为基类C,最后4字节为自身成员变量di
而E不仅覆盖了B和C的func0和func1,还有自身定义的虚函数func2,但这个func2 不会重新开辟一个虚函数表,而是跟在B的虚函数表后面
可以看下面的内存布局,第一个虚函数表为B的,有func0,但由于被覆盖了,因此类型为E,之后为E的func2
第二个虚函数表为C的,按照顺序先是C中定义的虚函数func,此后为E覆盖C的虚函数func1
1>class D size(16):
1> +---
1> 0 | +--- (base class C)
1> 0 | | {vfptr}
1> 4 | | c
1> | | <alignment member> (size=3)
1> 8 | | ci
1> | +---
1>12 | +--- (base class A)
1> | +---
1>12 | di
1> +---
1>
1>D::$vftable@:
1> | &D_meta
1> | 0
1> 0 | &D::func
1> 1 | &D::func1
1>
1>D::func this adjustor: 0
1>D::func1 this adjustor: 0
1>
1>class E size(24):
1> +---
1> 0 | +--- (base class B)
1> 0 | | {vfptr}
1> 4 | | bi
1> | +---
1> 8 | +--- (base class C)
1> 8 | | {vfptr}
1>12 | | c
1> | | <alignment member> (size=3)
1>16 | | ci
1> | +---
1>20 | ei
1> +---
1>
1>E::$vftable@B@:
1> | &E_meta
1> | 0
1> 0 | &E::func0
1> 1 | &E::func2
1>
1>E::$vftable@C@:
1> | -8
1> 0 | &C::func
1> 1 | &E::func1