类对象内存布局,虚函数,虚拟继承和多重继承的实现机制
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类对象的内存布局如下:
而在B中首先放的是指向虚函数表(简称vptr)的指针,本例中虚函数表的存放地址为0x4290756,B类对象的内存布局如下:
二.单一继承
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类对象内存布局如下:
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,谢谢合作!
如需所有源代码,请留邮箱地址。
分享到:
相关推荐
涉及各种情况下C++对象的sizeof大小,包括单一类对象,继承,重复继承 多继承 单一虚继承 等各种情况下的对象大小。对C++对象内存布局有清楚了解。
看了这个内存布局图详解之后,对于C++的了解更加深刻了,之前不懂得一头雾水的东西全都清楚了。
该资源为虚函数和虚继承及其结合的内存布局的测试,文中写明了有详细测试结果。
VC8.0上多重继承的内存布局
这是一些关于基类含有virtual函数或子类是virtual继承的对象的内存布局。其中有我截的一些图、内存布局图、文字说明,不过能力有限,说的不是很清楚,望谅解
1* 类如何布局? 2* 成员变量如何访问? 3* 成员函数如何访问? 4* 所谓的“调整块”(adjuster thunk)是... * 单继承、多重继承、虚继承 * 虚函数调用 * 强制转换到基类,或者强制转换到虚基类 * 异常处理
带虚函数的类对象的布局Windows平台下cl编译器:Linux平台下gcc编译器:Mac平台下clang编译器:对象布局虚函数表布局Offset_to_top
单继承下不含覆盖函数的类对象的布局Windows平台下cl编译器:Linux平台下gcc编译器:Mac平台下clang编译器:子类对象布局虚函数表布局注意虚函数
单继承下含覆盖函数的类对象的布局Windows平台下cl编译器:Linux平台下gcc编译器:Mac平台下clang编译器:子类对象布局虚函数表布局注意虚函数表
vc++类对象内存布局.jpg通过图解让你更容易理解哦呵呵....
C++ 内存布局虚继承 ---Empty virtual base classs (空虚基类).doc
C++对象模型在内存中的实现,讲述了类,继承以及虚继承的内存布局;成员变量和成员函数的访问已经访问时的开销情况,包含虚函数的情况,考察构造函数,析构函数,以及特殊的赋值操作符成员函数是如何工作的,数组是...
jvm jvm内存布局
C++对象内存布局[归类].pdf
菱形继承下类对象的布局Windows平台下cl编译器:Linux平台下gcc编译器:虚表值截取了部分截图Mac平台下clang编译器:子类对象布局虚函数表布局v
本文我们将阐释GCC编译器针对多重继承和虚拟继承下的对象内存布局。尽管在理想的使用环境中,一个C++程序员并不需要了解这些编译器内部实现细节,实际上,编译器针对多重继承(特别是虚拟继承)的各种实现细节对于我们...
博客《Cpp 对象模型探索 —— 含有虚基类的类的内存布局》的图片原文档,网址:https://blog.csdn.net/itworld123/article/details/102890062。
介绍C++对象在内存中是怎样分布的,有助于深层学习C++。
C++类继承内存布局文档打包