继承是C++作为OOD程序设计语言的三大特征(封装,继承,多态)之一,单一非多态继承是比较好理解的,本文主要讲解GCC环境下的多重继承和虚拟继承的对象内存布局。

一、多重继承

先看几个类的定义:

01 class Top
02  {
03 public:
04       int a;
05  };
06  
07  class Left : public Top
08  {
09 public:
10       int b;
11  };
12  
13  class Right : public Top
14  {
15 public:
16       int c;
17  };
18  
19  class Bottom : public Left, public Right
20  {
21 public:
22       int d;
23  };

不难想象,Left和Right类的内存布局如下图所示:

我们如下进行验证:

1 Left *left = new Left();
2 Top *top = left;
3 cout << left <<  '\t' << top << endl;//输出:0x902c008       0x902c008
4 Right *right = new Right();
5 top = 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’   
1 top = (Left *)bottom;
1 left = bottom;
2 cout << bottom << '\t' << top << '\t' << left << endl;//输出:0x9930028 0x9930028 0x9930028
3 top = (Right *)bottom;
4 right = bottom;
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。

好了,到这里讲完了非虚拟继承下的多重继承的内存布局情况,相信大家应该有一个比较清晰的认识了。最重要的一点是: 多重继承时,父类共同继承的祖父类会在子类中有多份存在。

二、虚拟继承

平时讨论的最多的是虚函数,很少涉及到虚拟继承的情况。那么,虚拟继承到底是一个什么概念呢?

先来看一个例子:

01 #include
<iostream>
02 using namespace std;
03  
04  class Father
05  {
06 public:
07     int a;
08  };
09  
10  class Child
virtual public Father
11  {
12  public:
13     int b;
14  };
15  
16  int main()
17  {
18     cout
<< 
sizeof(Father)
<< 
'\t' << sizeof(Child)
<< endl;
//输出:4  
12
19     Child
child;
20     cout
<< &child << 
'\t' <<
&child.b << 
'\t' <<
&child.a << endl;
//输出:0xbfc08124
0xbfc08128 0xbfc0812c
21     return 0;
22  }

对,你没有看错,类的大小输出不是4   8,而是4   12。虚拟继承时,编译器会在子类中安插上一个虚表指针。

从输出的对象成员地址来看,我们可以得到Child类的如下内存布局:

现在我们对多重继承的例子进行改造:

01 class Top
02 {
03 public:
04      int a;
05 };
06  
07 class Left
virtual public Top
08 {
09 public:
10      int b;
11 };
12  
13 class Right
virtual public Top
14 {
15 public:
16     int c;
17 };
18  
19 class Bottom
public Left, public Right
20 {
21 public:
22     int d;
23 };

把Left和Right改成了虚拟继承Top。

从上面验证简单虚拟继承时,编译器安插虚表指针的例子,我们可以想象出此时Bottom类的对象内存布局如下:

对,你没有看错!虚拟继承时,子类只有父类共同继承的祖父类的一份存在。这其实也就是虚拟继承的最大用途。此时,Top,Left,Right和Bottom对象的大小分别为:4  ,12  ,12 ,24。

既然有虚表指针了,那么Bottom的虚表是什么样的呢?请看:

有了虚表,内存布局情况一目了然。下面我们进行验证:

1 Bottom
*bottom = 
new Bottom();
2 top
= bottom;
3 cout
<< bottom << 
'\t' <<
top << endl;
//输出:0x9fa5028      
0x9fa503c
4 Left
*left = bottom;
5 cout
<< bottom << 
'\t' <<
left << endl;
//输出:0x9fa5028      
0x9fa5028
6 Right
*right = bottom;
7 cout
<< bottom << 
'\t' <<
right << endl;
//输出:0x9fa5028      
0x9fa5030

根据输出结果,我们可以知道指针的指向情况:

由于引入了虚指针和虚表,left指针和right指针可以根据虚表提供的偏移量信息,轻松访问到Top::a。

到此为止,已经讨论清楚了多重继承和虚拟继承下的对象内存布局情况。总结下:非虚拟多重继承时,子类会有父类

共同继承祖父类的多份存在;虚拟继承时,子类会被安插一个虚拟指针;多重虚拟继承时,子类只有父类共同继承祖父类的一

份存在。通过父类的虚拟指针,可以正确地访问祖父类中的成员。

浅析GCC下C++多重继承 & 虚拟继承的对象内存布局的更多相关文章

  1. 从汇编看c++的虚拟继承以及其内存布局(一)

    先看第一种最简单的情形,所有类中没有任何虚函数的菱形继承. 下面是c++源码: class Top {//虚基类 public: int i; Top(int ii) { i = ii; } }; c ...

  2. c++继承中的内存布局

    今天在网上看到了一篇写得非常好的文章,是有关c++类继承内存布局的.看了之后获益良多,现在转在我自己的博客里面,作为以后复习之用. ——谈VC++对象模型(美)简.格雷程化    译 译者前言 一个C ...

  3. C++各种类继承关系的内存布局

    body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; ...

  4. C++对象模型:单继承,多继承,虚继承,菱形虚继承,及其内存布局图

    C++目前使用的对象模型: 此模型下,nonstatic数据成员被置于每一个类的对象中,而static数据成员则被置于类对象之外,static和nonstatic函数也都放在类对象之外(通过函数指针指 ...

  5. C++ 虚函数表与多态 —— 多重继承的虚函数表 & 内存布局

    多重继承的虚函数表会有两个虚表指针,分别指向两个虚函数表,如下代码中的 vptr_s_1.vptr_s_2,Son类继承自 Father 和 Mather 类,并且改写了 Father::func_1 ...

  6. C++虚函数、虚继承、对象内存模型(转)

    参考:http://blog.csdn.net/hxz_qlh/article/details/14633361 需要注意的是虚继承.多重继承时类的大小.

  7. 从C++对象内存布局和构造过程来具体分析C++中的封装、继承、多态

    一.封装模型的内存布局 常见类对象的成员可能包含以下元素: 内建类型.指针.引用.组合对象.虚函数. 另一个角度的分类: 数据成员:静态.非静态 成员函数:静态.非静态.虚函数 1.仅包含内建类型的场 ...

  8. 转: c++继承中的内存布局

    英文原文: http://www.openrce.org/articles/files/jangrayhood.pdf 翻译: http://blog.csdn.net/jiangyi711/arti ...

  9. C++学习笔记----4.5 C++继承时的对象内存模型

    推荐阅读:http://blog.csdn.net/randyjiawenjie/article/details/6693337 最近研究了一下,C++继承的内存对象模型.主要是读了读http://b ...

随机推荐

  1. mysql 基本的操作数据库命令

    注意:命令操作都是分号结尾 1 .连接mysql: mysql -u  用户名   -p 密码 2.展示所有数据库: show  databases; 3.进入数据库: use   数据库名字; 4. ...

  2. 分布式日志收集框架Flume

    分布式日志收集框架Flume 1.业务现状分析 WebServer/ApplicationServer分散在各个机器上 想在大数据平台Hadoop进行统计分析 日志如何收集到Hadoop平台上 解决方 ...

  3. Linux多进程CS服务器简单测试

    Linux多进程CS服务器简单实现 server端 多进程实现多用户连接,即每个用户一个连接,这里仍然用server将收到的字符串转大写后返回给客户端. 代码实现 #include <stdio ...

  4. python自学之第一章——列表(一)

    1.列表是什么? 列表[list]:列表是由一系列特定顺序排列的元素组成. 列表由[]表示 eg: a = ['jackal','jax';'jack','jeef','jacky'] print(a ...

  5. Python3 图像识别(一)

    Infi-chu: http://www.cnblogs.com/Infi-chu/ 一.环境准备: 1.Python3.x(我是用的是Python3.6.5),这个问题不大,只要3.4以上就OK. ...

  6. golang channel 使用总结

    原文地址 不同于传统的多线程并发模型使用共享内存来实现线程间通信的方式,golang 的哲学是通过 channel 进行协程(goroutine)之间的通信来实现数据共享: Do not commun ...

  7. 20155236 2016-2017-2 《Java程序设计》第一周学习总结

    20155236 2016-2017-2 <Java程序设计>第1周学习总结 教材学习内容总结 第一章:java平台概论 java SE(j2SE)分为四个主要的部分:JVM.JRE.JD ...

  8. 20155315庄艺霖第三次作业之Linux初体验

    Linux初体验 安装Linux三两事 老师的作业要求基于VirtualBox安装Linux系统,我一开始下载了VB但是电脑运行不了,后来看网上的教程下载了VMware,才算开始了我的Linux之旅. ...

  9. 20155325《Java程序设计》实验一(Java开发环境的熟悉)实验报告

    实验一Java开发环境的熟悉-1 提交 Linux或Window或macOS中命令行下运行Java的截图 运行结果包含自己学号等证明自己身份的信息 实验一Java开发环境的熟悉-2 提交 Linux或 ...

  10. 20155338 2016-2017-2 《Java程序设计》第九周学习总结

    20155338 2016-2017-2 <Java程序设计>第九周学习总结 教材学习内容总结 第十六章 整合数据库 JDBC入门 · 数据库本身是个独立运行的应用程序 · 撰写应用程序是 ...