您的位置:首页 > 编程语言 > C语言/C++

【c++】深入剖析虚拟继承与各种继承关系中派生类内成员内存分布情况及虚基类表的内容

2016-11-13 23:31 627 查看
概要

本文讲述在VS2012环境下,采用代码和图结合的方法,分析C++代码中不同继承方式的对象模型,以及从汇编角度分析虚拟继承编译器生成的虚基类表里的内容,不涉及虚函数。

继承分类:

1.单继承

一个子类只有一个直接父类

// 单继承  工人类 继承 人类
class Person
{
//...
};
class Worker : public Person
{
//...
};




2.多继承

一个子类有两个以上直接父类

// 多继承 农民工类分别继承 工人类 和 农民类
class Worker
{
// ...
};
class Farmer
{
// ...
};
class MigrantWorker : public Worker, public Farmer
{
// ..
};




3.多重继承

继承关系大于两层

// 多继承 工人类继承 Person类 农民工类继承Worker类
class Person
{
// ...
};
class Worker : Person
{
// ...
};
class MigrantWorker : public Worker
{
// ..
};




4.菱形继承

两个子类继承同一个父类,而又有子类同时继承这两个子类。

// 菱形继承也叫钻石继承
// 工人类继承人类, 农民继承人类, 农民工类继承工人类和农民类
class Person
{
//...
};
class Worker : public Person
{
//...
};
class Framer : public Person
{
//...
};
class MigrantWorker : public Worker,public Framer
{
//...
};




派生类内存分布情况:
1.单继承



2.多继承



我们改变Worker类 和 Farmer类的继承顺序再观察:



3.多重继承



4.菱形继承



在MigrantWorker的对象中有两份Person的成员,导致菱形继承存在二义性和数据冗余的问题,而虚拟继承可以很好地解决菱形继承的二义性和数据冗余问题。

虚拟继承:

虚拟继承与单继承:

// 工人类虚继承 Person类
class Person
{
public:
int dataPerson;
};
class Worker : virtual public Person
{
public:
int dataWorker;
};

int main()
{
Worker w;
w.dataPerson = 1;
w.dataWorker = 2;
return 0;
}


内存分布图情况:





我们可以看到在对象w的成员数据之前多了4个字节,下面我们来分析一下这四个字节是什么.

先打开看一看里面存的是什么:






里面存储了0和8这又是什么呢?

首先进入派生类的构造函数:






进入构造函数内:






然后继续执行下去直到ret返回后:






然后继续执行:






然后下一步,mov dword ptr w[ecx],1 即在派生类对象首地址往后偏移ECX(8)个字节然后存入1,而派生类则是利用ebp-XX 的方式栈帧赋值。至此我们已经搞清楚“表”里的08存储的是什么,即:基类对象成员在派生类对象中与派生类对象首地址之间的偏移量。00到底是什么请继续看下面的分析。

虚拟继承与多继承:


(1)先继承的类为虚基类时

内存分布图:






赋值情况:






(2)后继承的类为虚基类时

内存分布图:






赋值情况:






(3)继承的两个类都为虚基类时

内存分布:






赋值情况:






虚拟继承与多重继承:


(1)最上层类是虚基类时:

内存分布图:






对比多继承中表的值我们可以发现,表中前四个字节到底是什么:派生类对象首地址与派生类中存储的的表的指针之间的偏移量。

在这里表的指针存储在对象的前四个字节,相对派生类对象地址偏移量为0,所以表中前四个字节为0.

而在多重继承中的前两种情况中,表指针存储在派生类对象向后偏移4个字节出,也就是表的指针往前偏移4个字节,即-4,在内存中-4存储的是补码也就是我们说看到的fc ff ff  ff。

至此我们已经弄明白了表中到底存储的是什么,也要给这个表(这个地址)一个名字,它叫虚基类表指针。在这里不深究该表的来源,暂且理解为编译器的功劳。

赋值情况:

经过前面的观察和对比我们发现,赋值情况只有虚基类才会取偏移地址赋值,虚基类表中存放的内容也分析清楚,对象内存分布也分析清楚,此后不再赘述,只放上成员内存分布图。

(2)中间层是虚基类时:



内存分布图:





菱形继承

内存分布:






分布顺序分析:






进Worker构造函数:


4

Framer构造函数:



赋值情况:



虚继承虽然解决的菱形继承里子类对象包含对分父类对象的数据冗余浪费空间的问题,单因为要给对象分配内存去存储虚表,也带来了性能上的损耗。

_________________________________________________________END______________________________________________________________________
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐