`
xitonga
  • 浏览: 586627 次
文章分类
社区版块
存档分类
最新评论

类对象内存布局,虚函数,虚拟继承和多重继承的实现

 
阅读更多

类对象内存布局,虚函数,虚拟继承和多重继承的实现机制

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,谢谢合作!

如需所有源代码,请留邮箱地址。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics