1. 多重继承的内存布局
struct A
{
int a;
};
struct B:public A
{
int b;
};
struct C:public A
{
int c;
}
struct D
{
int d;
}
多重继承的情况, 如果父类有共同的祖父类,则祖父类对象被拷贝了多次。在该例中,B的内存布局为(假设从上往下为地址增加方向)
int A::a
int B: b
则D的内存布局为
int B::A:;a
int B:;b
int A::a
int C::c
int D::d
如果sizeof各个类的大小,很明显
sizeof(A) = 4;
sizeof(B) = 8;
sizeof(C) = 8;
sizeof(D) = 20
如果有这样的语句
D *pd = new D;
B *pa = pd;
C *pb = pd;
A *pa = pd;
我们分别输出pa,pd,pc和pd的地址试试
pb = 0x8650a10
pc = 0x8650a18
pd = 0x8650a10
注意,这里pd和pb是相同的,都指向pd的开始位置,但是pc和pb不同,pc产生了两个4字节的偏移,即指向D中C对象的存储位置
pa呢,很明显是具有二义性的,它既可以指向pb中的a,也可以指向pc中的a,自然就会报错。所以上述语句会出现编译错误。2. 具有虚函数的多重继承
如果多重继承包含虚函数
struct A
{
int a;
virtual void f1() {}
};
struct B: public A
{
int b;
virtual void f2() {}
};
struct C: public A
{
int c;
virtual void f3() {}
};
struct D: public B, public C
{
int d;
virtual void f4() {}
};
此时,A的内存布局为
A::vfptr
int A::a
其中vfptr为指向虚函数表的指针
那么B的布局自然为
A::vfptr
int A::a
int B::b
D的布局为
B::A::vfptr
int B::A::a
int B::b
C::A::vfptr
int C::A::a
int C::c
int D::d
这时各类的大小为
sizeof(A) = 8;
sizeof(B) = 12;
sizeof(C) = 12;
sizeof(D) = 28;
同样指针的位置为
pb = 0x8650a10
pc = 0x8650a1c
pd = 0x8650a10
pa仍然是二义性的
另外注意f4函数,这时D类自定义的虚函数,那么它放在什么地方呢?应该放在第一虚函数表里,也就是B::A::vfptr指向的虚函数表中
3. 虚继承的内存布局
struct A
{
int a;
};
struct B:virtual public A
{
int b;
};
struct C:virtual public A
{
int c;
}
struct D
{
int d;
}
如果是虚继承,那么在D中只有一个虚基类A的对象,这时就不会出现二义性的情况了
先看B的内存布局,由于B是虚继承A,因此B中会有一个指向虚表的指针,虚表里面存储有当前位置到虚基类的偏移和到当前类的开始位置处的偏移,例如B的内存布局
B::vptr
int B::b
int A::a
可以看到,a的位置放在了最底部,也就是内存地址最高的地方。vptr指向一个虚表,虚表中存储到虚基类的偏移,即8和到当前类的开始处的偏移,这里是0
因此
如果有
pb = new B;
pa = pb;