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

2014-11-24 02:55:19 · 作者: · 浏览: 0

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

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:"<
  

   

结果如下:

\

< http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+utzD98/Uo6xBwOC21M/ztcTE2rTmsry+1sjnz8KjujwvcD4KCjx0YWJsZSBib3JkZXI9"1" cellspacing="0" cellpadding="0">

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 :"<
    
     可得到结果:

     

\ \

从中可以观察出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,谢谢合作!

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