类对象内存布局,虚函数,虚拟继承和多重继承的实现机制
1. 无继承关系的类
2. 单一继承
2.1单层继承
2.2多重继承
3. 多重继承
一.无继承关系的类
已知A类与B类,A类代表无虚函数的类,B类代表有虚函数的类,下文都用字母表示上文多提到的类类型。
classA{ public: int a_1; int a_2; A(int v1 = 1, int v2 =2) :a_1(v1),a_2(v2){} void show(){ cout<< "class A" << endl; } }; classB{ public: int b_1; int b_2; B(int v1 = 1, int v2 =2) :b_1(v1),b_2(v2){} void virtual show(){ cout<< "class B" << endl; } }; 做如下测试: { //无继承关系类对象的大小 cout<<"baseclass objects size:"<<endl; cout<<"sizeof(A)="<<sizeof(A)<<endl; cout<<"sizeof(B)="<<sizeof(B)<<endl<<endl; //无继承关系类对象的数据成员在内存中的读取 Aa(1,2); Bb(1,2); int *pa = (int*)&a; cout<< "data member address and its value inclass A object" << endl; cout<<pa << " " << pa[0] << endl; cout<<pa + 1 << " " << pa[1] <<endl; int *pb = (int*)&b; cout<< "data member address and its value inclass B object" << endl; cout<<pb << " " << pb[0] << endl; cout<<pb + 1 << " " << pb[1] <<endl; cout<<pb + 1 << " " << pb[2] <<endl; }
结果如下:
很明显,A类对象的内存布局如下:
int a_1 |
int a_2 |
而在B中首先放的是指向虚函数表(简称vptr)的指针,本例中虚函数表的存放地址为0x4290756,B类对象的内存布局如下:
vptr_B |
int b_1 |
int b_2 |
二.单一继承
1.单层继承
1)派生于A类的子类:
ClassC:内不增虚函数,普通继承
ClassD:内设虚函数,普通继承
ClassG:内不增虚函数,虚拟继承
ClassH:内设虚函数,虚拟继承
用UML类图如下:
测试代码如下:
{ //类对象的大小 cout<<"class objects‘ size derived from Class A :"<<endl; cout<<"sizeof(C)="<<sizeof(C)<<endl; cout<<"sizeof(D)="<<sizeof(D)<<endl; cout<<"sizeof(G)="<<sizeof(G)<<endl; cout<<"sizeof(H)="<<sizeof(H)<<endl<<endl; //数据成员在内存中的分布与读取 C c(1,2,3); int *pc = (int*)&c; cout << "data member address and its value in class C object" << endl; cout <<pc << " " << pc[0] << endl; cout <<pc + 1 << " " << pc[1] << endl; cout <<pc + 2 << " " << pc[2] << endl<<endl; D d(1,2,3); int *pd = (int*)&d; cout << "data member address and its value in class D object" << endl; cout <<pd << " " << pd[0] << endl; //vptr,指向虚函数表的指针 cout <<pd + 1 << " " << pd[1] << endl; cout <<pd + 2 << " " << pd[2] << endl; cout <<pd + 3 << " " << pd[3] << endl; cout <<"d: virtual function table"<<endl; int *vptr_d = (int*)(pd[0]);//获取虚函数表中第一个虚函数地址 cout <<vptr_d[0]<<endl<<endl; G g(1,2,3); int *pg = (int*)&g; cout << "data member address and its value in class G object" << endl; cout <<pg << " " << pg[0] << endl;//vbptr,指向存放虚基类子对象偏移量表的指针 cout <<pg + 1 << " " << pg[1] << endl; cout <<pg + 2 << " " << pg[2] << endl; cout <<pg + 3 << " " << pg[3] << endl; cout <<"g: virtual base class table"<<endl; int *vbptr_g = (int*)(pg[0]);//获取虚基类表中第一个偏移量 cout <<vbptr_g[0]<<" "<<vbptr_g[1]<<" "<<endl<<endl; H h(1,2,3); int *ph = (int*)&h; cout << "data member address and its value in class H object" << endl; cout <<ph << " " << ph[0] << endl; cout <<ph + 1 << " " << ph[1] << endl; cout <<ph + 2 << " " << ph[2] << endl; cout <<ph + 3 << " " << ph[3] << endl; cout <<ph + 4 << " " << ph[4] << endl; cout <<"h: virtual function table and virtual base class table"<<endl; int *vptr_h = (int*)(ph[0]); int *vbptr_h = (int*)(ph[1]); cout <<vptr_h[0]<<endl;//虚函数的地址 cout <<vbptr_h[0] <<" "<<vbptr_h[1]<<" " << endl;//虚拟继承后,h中虚基类子对象的偏移量 }可得到结果:
从中可以观察出C类对象内存布局如下:
int a_1 |
int a_2 |
int c_1 |
D类对象:由于类D中引进了虚函数,会在D对象内存起始处存放一个vptr指向的虚函数表存放一个虚函数地址,其值为0x04264351(用&D::display表示虚函数D::display()地址)。
内存布局如下:
G类对象:由于是虚拟继承,故会在g内存起始处存放一个vbptr,指向虚基表的指针,虚函数表存放两个偏移地址分别为0和8,其中0表示g对象地址相对与存放vptr指针的地址的偏移量,8表示g对象中a对象部分相对于存放vbptr指针的地址的偏移量(用&h(a)-&vbpt表示,其中&h(a)表示对象h中a部分的地址),并且会将共享部分放到不变部分后面,这些分配是由编译器在G的构造函数隐式完成,g内存布局如下:
H类对象:这里既增加了一个虚函数,又是以虚拟方式继承,所以在内存中会分配两个指针:vptr_h和vbptr_h,根据程序以及显示结果容易判断vptr_h放在vbptr_h前面。h内存布局如下:
2)派生于B类的子类:
Class E:内不增虚函数,普通继承
ClassF:内设虚函数,普通继承
ClassI:内不增虚函数,虚拟继承
ClassJ:内设虚函数,虚拟继承
UML图标是他们之间的关系如下:
按照上述相同的测试方法,得到结果:
从中可以观察出各个对象内存布局如下:
E类对象:当不是虚拟继承时,基类中出现虚函数,派生类对象内存布局开始处就会指向派生类中的虚函数(不论是继承下来的还是派生类覆盖后的)。但是如果是虚拟继承,情况就不一样,可在下文中观察得到。e内存布局如下:
F类对象:当基类有虚函数,派生类中内设了新的虚函数,并且继承方式为单一非虚拟继承时,派生类中只有与一张虚拟函数表,指向基类和派生类中的虚拟函数,如本例中的f,内存布局如下:
I 类对象:当用构造函数来构造I类对象时,会在共享区和不变区之间留下1字节内存(存放的是0),故所占内存24bytes,但其实必须要的只要20bytes就可以实现。类似的情况在J类中也出现。Walkerczb经过简单测试后,发现基类有虚函数,采用虚拟继承的派生类如用构造函数构造时,派生类对象内存的共享部分和不变部分之间会留出1byte内存;但如果用直接赋值的方式来构造派生类对象,则不会。
以下采用
I i; i.a_1=1; i.a_2=2; i.i_1=3
这种直接赋值方式构造出来的i对象,同样的方法作用于f对象,其测试结果如下:
这里编者不理解调用构造函数出现多余内存块的实现机制,故采用直接赋值方式构造对象来说明i和j的内存布局,其中i内存布局如下(vbptr_I_B表示一个指向虚函数表的指针,该虚函数表存放的是基类B虚函数在I中的实现):
J类对象:J类在I类基础上新内设了一个虚函数。其对象会在内存开始处存放一个vptr指向该新增的虚函数,内存布局如下:
2.多层继承(不是多重继承哦!)
限于篇幅,这里只举出两层虚拟继承,且基类和派生类中都有虚函数的情况作分析。做UML类图如下:
测试结果如下:
其内存布局如下:
1:存放vptr_K指针,该指针指向的虚函数表存放新增加的虚函数地址
2:存放vbptr_k指向,该指针指向的虚基类表存放各基类子对象部分相对于存放vbptr_k的地址的偏移量
3:存放z_1,表示该类新增加的数据成员
4:存vptr_k_B,该指针指向的虚函数表存放最深的基类中的虚函数地址
5/6:存放a_1,a_2,表示最深层基类的数据成员
7.存放vptr_K_J,该指针指向的虚函数表存放在继承层次中J类增加的虚函数
8.存放vbptr_K_J,该指针指向的虚基类表存放在继承层次中J类各个基类子对象部分相对于存放vbptr_K_J的地址的偏移量。
9.存放y_1,表示J类的数据成员
在k对象中,需要注意的是B类子对象部分存放在J类子对象部分前面,这是为了实现多重继承*享虚基类所做的处理。
三.多重继承
多重继承详细讨论太过复杂,这里指给出两种最基本的情况,其余情形可根据这两种基本情况结合以上分析得知内存布局:
1. 虚拟继承于两个类
测试后,其结果如下:
可知L类对象内存布局如下:
2. 钻石型继承:已知两个类虚拟继承于同一基类,后再有类继承于两个虚拟派生类,用UML类图表示如下:
其测试结果如下:
M类对象内存布局如下:
总结:
本文从无继承关系的类,单一继承类,多重继承类三方面,剖析了类对象内存布局以及虚函数,虚拟继承和多重继承在内存分配上的实现。文章如有错误,请读者留言,万分感谢!
附注:
转载请注明出处:http://blog.csdn.net/walkerkalr,谢谢合作!
如需所有源代码,请留邮箱地址。