继承是C++作为OOD程序设计语言的三大特征(封装,继承,多态)之一,单一非多态继承是比较好理解的,本文主要讲解GCC环境下的多重继承和虚拟继承的对象内存布局。
一、多重继承
先看几个类的定义:
07 |
class Left : public Top |
13 |
class Right : public Top |
19 |
class Bottom : public Left, public Right |
不难想象,Left和Right类的内存布局如下图所示:

我们如下进行验证:
1 |
Left *left = new Left(); |
3 |
cout << left << '\t' << top << endl; //输出:0x902c008 0x902c008 |
4 |
Right *right = new Right(); |
6 |
cout << right << '\t' << top << endl; //输出:0x902c018 0x902c018 |
从输出结果可以看出,父类指针top指向子类对象left和right的起始地址,与上述内存布局吻合。
在非虚拟多重继承的情况下,子类的内存布局是什么样子的呢?如下所示:

可以看出,Bottom类由于继承了Left和Right,而Left和Right又分别继承了Top。因此,Bottom包含了Top两次!
下面进行验证:
1 |
Bottom *bottom = new Bottom(); |
1 |
// top = bottom; //error: ‘Top’ is an ambiguous base of ‘Bottom’ |
2 |
cout << bottom << '\t' << top << '\t' << left << endl; //输出:0x9930028 0x9930028 0x9930028 |
5 |
cout << bottom << '\t' << top << '\t' << right << endl; //输出:0x9930028 0x9930030 0x9930030 |
从输出结果可以看出,left指针和right指针分别指向了bottom对象中它们所处的位置:
由于bottom对象中存在两部分top对象,因此不能直接用top指针指向bottom对象,因为编译器不知道你的意图到底是指向left中的
bottom部分,还是right中的bottom部分。需要进行转换才可以。如果需要通过bottom指针分别访问left和right中的top部
分,可以如下: bottom->Left::a, bottom->Right::a。
好了,到这里讲完了非虚拟继承下的多重继承的内存布局情况,相信大家应该有一个比较清晰的认识了。最重要的一点是: 多重继承时,父类共同继承的祖父类会在子类中有多份存在。
二、虚拟继承
平时讨论的最多的是虚函数,很少涉及到虚拟继承的情况。那么,虚拟继承到底是一个什么概念呢?
先来看一个例子:
10 |
class Child
: virtual public Father |
18 |
cout
<< sizeof (Father)
<< '\t' << sizeof (Child)
<< endl; //输出:4
12 |
20 |
cout
<< &child << '\t' <<
&child.b << '\t' <<
&child.a << endl; //输出:0xbfc08124
0xbfc08128 0xbfc0812c |
对,你没有看错,类的大小输出不是4 8,而是4 12。虚拟继承时,编译器会在子类中安插上一个虚表指针。
从输出的对象成员地址来看,我们可以得到Child类的如下内存布局:

现在我们对多重继承的例子进行改造:
07 |
class Left
: virtual public Top |
13 |
class Right
: virtual public Top |
19 |
class Bottom
: public Left, public Right |
把Left和Right改成了虚拟继承Top。
从上面验证简单虚拟继承时,编译器安插虚表指针的例子,我们可以想象出此时Bottom类的对象内存布局如下:

对,你没有看错!虚拟继承时,子类只有父类共同继承的祖父类的一份存在。这其实也就是虚拟继承的最大用途。此时,Top,Left,Right和Bottom对象的大小分别为:4 ,12 ,12 ,24。
既然有虚表指针了,那么Bottom的虚表是什么样的呢?请看:

有了虚表,内存布局情况一目了然。下面我们进行验证:
1 |
Bottom
*bottom = new Bottom(); |
3 |
cout
<< bottom << '\t' <<
top << endl; //输出:0x9fa5028
0x9fa503c |
5 |
cout
<< bottom << '\t' <<
left << endl; //输出:0x9fa5028
0x9fa5028 |
7 |
cout
<< bottom << '\t' <<
right << endl; //输出:0x9fa5028
0x9fa5030 |
根据输出结果,我们可以知道指针的指向情况:

由于引入了虚指针和虚表,left指针和right指针可以根据虚表提供的偏移量信息,轻松访问到Top::a。
到此为止,已经讨论清楚了多重继承和虚拟继承下的对象内存布局情况。总结下:非虚拟多重继承时,子类会有父类
共同继承祖父类的多份存在;虚拟继承时,子类会被安插一个虚拟指针;多重虚拟继承时,子类只有父类共同继承祖父类的一
份存在。通过父类的虚拟指针,可以正确地访问祖父类中的成员。
- 从汇编看c++的虚拟继承以及其内存布局(一)
先看第一种最简单的情形,所有类中没有任何虚函数的菱形继承. 下面是c++源码: class Top {//虚基类 public: int i; Top(int ii) { i = ii; } }; c ...
- c++继承中的内存布局
今天在网上看到了一篇写得非常好的文章,是有关c++类继承内存布局的.看了之后获益良多,现在转在我自己的博客里面,作为以后复习之用. ——谈VC++对象模型(美)简.格雷程化 译 译者前言 一个C ...
- C++各种类继承关系的内存布局
body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; ...
- C++对象模型:单继承,多继承,虚继承,菱形虚继承,及其内存布局图
C++目前使用的对象模型: 此模型下,nonstatic数据成员被置于每一个类的对象中,而static数据成员则被置于类对象之外,static和nonstatic函数也都放在类对象之外(通过函数指针指 ...
- C++ 虚函数表与多态 —— 多重继承的虚函数表 & 内存布局
多重继承的虚函数表会有两个虚表指针,分别指向两个虚函数表,如下代码中的 vptr_s_1.vptr_s_2,Son类继承自 Father 和 Mather 类,并且改写了 Father::func_1 ...
- C++虚函数、虚继承、对象内存模型(转)
参考:http://blog.csdn.net/hxz_qlh/article/details/14633361 需要注意的是虚继承.多重继承时类的大小.
- 从C++对象内存布局和构造过程来具体分析C++中的封装、继承、多态
一.封装模型的内存布局 常见类对象的成员可能包含以下元素: 内建类型.指针.引用.组合对象.虚函数. 另一个角度的分类: 数据成员:静态.非静态 成员函数:静态.非静态.虚函数 1.仅包含内建类型的场 ...
- 转: c++继承中的内存布局
英文原文: http://www.openrce.org/articles/files/jangrayhood.pdf 翻译: http://blog.csdn.net/jiangyi711/arti ...
- C++学习笔记----4.5 C++继承时的对象内存模型
推荐阅读:http://blog.csdn.net/randyjiawenjie/article/details/6693337 最近研究了一下,C++继承的内存对象模型.主要是读了读http://b ...
随机推荐
- QEMU 模拟运行 VxWorks 6.6
QEMU 模拟运行 VxWorks 6.6 项目简介 本项目是在 Windows 系统编译运行 X86 平台 VxWorks 6.6 系统,使用的模拟软件是 qemu for Windows Host ...
- kubernetes常用基础命令
创建资源对象 创建名为nginx-deploy的控制器资源对象 [root@master ~]# kubectl run nginx-deploy --image=nginx:1.12 --repli ...
- U盘,移动硬盘显示显示需要格式化怎么修复
冷静别怕,用windows系统自带磁盘修复CHKDSK命令即可解决此问题. 首先,在开始菜单栏站到“运行”窗口,也就是c:\(命令提示符),win7可直接在开始菜单的搜索窗口中输入“cmd”. 其次, ...
- 核密度估计 Kernel Density Estimation (KDE) MATLAB
对于已经得到的样本集,核密度估计是一种可以求得样本的分布的概率密度函数的方法: 通过选取核函数和合适的带宽,可以得到样本的distribution probability,在这里核函数选取标准正态分布 ...
- JournalNode的作用
NameNode之间共享数据(NFS .Quorum Journal Node(用得多)) 两个NameNode为了数据同步,会通过一组称作JournalNodes的独立进程进行相互通信.当activ ...
- SSM-CRUD入门项目——删除
删除 分析 可以进行单个删除,直接点击每条记录后的删除按钮 批量删除,通过勾选checkbox框进行选择删除 单个删除: 通过发送DELETE请求的URL:/emp/{id} 这次我们先从contro ...
- 20155213 2016-2017-2《Java程序设计》课程总结
20155213 2016-2017-2<Java程序设计>课程总结 每周作业链接汇总 预备作业1:我所期望的师生关系 预备作业2:C语言的学习的回忆 预备作业3:小议linux 第一周作 ...
- 20155305 2016-2017-2 《Java程序设计》实验三 敏捷开发与XP实践
20155305 2016-2017-2 <Java程序设计>实验三 敏捷开发与XP实践 实验内容 XP基础 XP核心实践 相关工具 实验步骤 (一)敏捷开发与XP 1.敏捷开发 敏捷开发 ...
- 20145226夏艺华 《Java程序设计》实验报告二
实验二 Java面向对象程序设计 实验内容 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟悉S.O.L.I.D原则 了解设计模式 实验步骤 (一)单元测试 ...
- 通过unixODBC访问PostgreSQL数据库
磨砺技术珠矶,践行数据之道,追求卓越价值回到上一级页面:PostgreSQL基础知识与基本操作索引页 回到顶级页面:PostgreSQL索引页[作者 高健@博客园 luckyjackgao@g ...