对虚继承层次的对象的内存布局,在不同编译器实现有所区别。
首先,说说GCC的编译器.
它实现比较简单,不管是否虚继承,GCC都是将虚表指针在整个继承关系*享的,不共享的是指向虚基类的指针。
class A {
int a;
virtual ~A(){}
};
class B:virtual public A{
virtual void myfunB(){}
};
class C:virtual public A{
virtual void myfunC(){}
};
class D:public B,public C{
virtual void myfunD(){}
};
以上代码中 sizeof(A)=8,sizeof(B)=12,sizeof(C)=12,sizeof(D)=16.
解释:A中int+虚表指针。B,C中由于是虚继承因此大小为A+指向虚基类的指针,B,C虽然加入了自己的虚函数,但是虚表指针是和基类共享的,因此不会有自己的虚表指针。D由于B,C都是虚继承,因此D只包含一个A的副本,于是D大小就等于A+B中的指向虚基类的指针+C中的指向虚基类的指针。
如果B,C不是虚继承,而是普通继承的话,那么A,B,C的大小都是8(没有指向虚基类的指针了),而D由于不是虚继承,因此包含两个A副本,大小为16. 注意此时虽然D的大小和虚继承一样,但是内存布局却不同。
然后,来看看VC的编译器
vc对虚表指针的处理比GCC复杂,它根据是否为虚继承来判断是否在继承关系*享虚表指针,而对指向虚基类的指针和GCC一样是不共享,当然也不可能共享。
代码同上。
运行结果将会是sizeof(A)=8,sizeof(B)=16,sizeof(C)=16,sizeof(D)=24.
解释:A中依然是int+虚表指针。B,C中由于是虚继承因此虚表指针不共享,由于B,C加入了自己的虚函数,所以B,C分别自己维护一个虚表指针,它指向自己的虚函数。(注意:只有子类有新的虚函数时,编译器才会在子类中添加虚表指针)因此B,C大小为A+自己的虚表指针+指向虚基类的指针。D由于B,C都是虚继承,因此D只包含一个A的副本,同时D是从B,C普通继承的,而不是虚继承的,因此没有自己的虚表指针。于是D大小就等于A+B的虚表指针+C的虚表指针+B中的指向虚基类的指针+C中的指向虚基类的指针。
同样,如果去掉虚继承,结果将和GCC结果一样,A,B,C都是8,D为16,原因就是VC的编译器对于非虚继承,父类和子类是共享虚表指针的。
以下例子都是在VC上实验过的:
例一:
class A {
int a;
virtual ~A(){}
};
class B:virtual public A{
virtual void myfunB(){};
};
class BB: public A{
virtual void myfunB(){};
};
class C:virtual public A{
virtual void myfunC(){}
};
class CC:virtual public A{
virtual void myfunC(){}
};
class D:public B,public C{
virtual void myfunD(){}
};
class DD:public BB,public CC{
virtual void myfunD(){}
};
class DDD:public BB,public C{
virtual void myfunD(){}
};
class D0:virtual public B,public C{
virtual void myfunD(){}
};
class D00:virtual public B,virtual public C{
virtual void myfunD(){}
};
class D1:virtual public BB, public CC{
virtual void myfunD(){}
};
class D11:virtual public BB,virtual public CC{
virtual void myfunD(){}
};
cout<<sizeof(A)<<"\n"<<sizeof(B)<<'\n'<<sizeof(C)<<'\n'<<sizeof(D)<<endl; //8 16 16 24
cout<<sizeof(BB)<<'\n'<<sizeof(CC)<<'\n'<<sizeof(DD)<<'\n'<<sizeof(DDD)<<endl;//8 16 24 24
cout<<sizeof(D0)<<'\n'<<sizeof(D00)<<'\n'<<sizeof(D1)<<'\n'<<sizeof(D11)<<endl;//24 32 24 32
例二:
1.class K{}; sizeof(K) = 1;
2. class A{ int a; void a(); int aa();}; sizeof(A) = 4;
3. class B{ int b; virtual void b(); }; sizeof(B) = 4+4;
4. class C0:public A,B { }; sizeof(C)=12
class C1:public B{ int c; virtual void c(); }; sizeof(C1)=4+4+4=12
class C:virtual public B{int c; virtual void c();}sizeof(C)=4+4+4+4+4=20 //虚继承时VC中基类和子类不共享虚指针
5. class D:virtual public B{int d; virtual void d();}sizeof(D)=4+4+4+4+4=20 //虚继承时VC中基类和子类不共享虚指针
6. class E:public C,public D{ int e;} sizeof(E) = 36//B+C与D的两个虚指针+C与D的本身成员+E成员
class E: virtual public C,public D{ int e; } sizeof(E) = 36
class E: virtual public C, virtual public D{ int e;} sizeof(E) =36
例三:
class A{};
class B
{
int a;
char c;
static int s;
};
class C
{
int d;
virtual void f();
};
class D:virtual public B
{
double dd;
};
class E:public C
{
virtual void f1();
};
class F:virtual public B
{
};
class G:public D,F
{
};
int a[20];
int *p;
double *da[20];
cout<<"double类型 "<<sizeof(double)<<endl;//double类型占有8个字节
cout<<"整型数组 "<<sizeof(a)<<endl;//数组占有4*20个字节
cout<<"int类型指针 "<<sizeof(p)<<endl;//指针占有4个字节
cout<<"double指针数组 "<<sizeof(da)<<endl;//double指针类型的数组占有4*20个字节,因为指针式占有四个字节的
cout<<"empty class "<<sizeof(A)<<endl;//空类占有1个字节,因为每个实例在内存中都有一个独一无二的地址,
为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址
cout<<"带有两个普通成员一个静态成员的类 "<<sizeof(B)<<endl;//B类的大小为8,因为内存对齐,所以c后面有3个字节是空的,而s是static类型属于全局数据区的
cout<<"带有虚函数的类 "<<sizeof(C)<<endl;//int型4个字节,虚指针4个字节 8
cout<<"虚拟继承 "<<sizeof(D)<<endl;//double占8个字节,基类中的int和char类型共占8个字节,多三个字节空着,虚指针占4个字节,多4个空着为了字节对齐 24
cout<<"基类中有虚函数,本类中也有虚函数 "<<sizeof(E)<<endl;//基类C中的int型占四个字节,基类和派生类中的虚函数共用一个虚指针占用4个字节 8
cout<<"虚拟继承的类 "<<sizeof(F)<<endl;//从基类中派生过来的成员有8个字节,虚指针4个字节,共12个字节
cout<<"多重继承 "<<sizeof(G)<<endl;//从基类中得到的成员共D类中double8个字节,B类中int和char共8个字节,D类和F类中分别8个虚指针字节所以共32个字节
总结:sizeof 类 = 成员变量+虚函数指针(void变量 static变量 普通函数 均不算在内)。
sizeof 继承类 = 本身成员变量+父类的大小,如果是n个平级虚拟继成的话,则另外加n个指向父类指针的大小;而对于虚拟成员变量,一般情况下共享一个虚拟指针,VC平台上虚继承时父类和子类虚表指针不共享(比非虚继承时大小大的多)。
多重继承的时候(有多层继承级别),若通过不同继承渠道有相同的父类,渠道含虚继承则只得到一套此父类的成员,不含虚继承则可能得到若干套此父类的成员