测试一、虚继承与继承的区别

1.1  单个继承,不带虚函数
1>class B size(8):
1> +---
1> 0 | +--- (base class A)
1> 0 | | _ia //4B
1> | +---
1> 4 | _ib //4B

有两个int类型数据成员,占8B,基类逻辑存在前面

1.2、单个虚继承,不带虚函数
1>class B size(12):
1> +---
1> 0 | {vbptr} //虚基指针(指向虚基表)
1> 4 | _ib //派生类放到前面
1> +---
1> +--- (virtual base A) //虚基类
1> 8 | _ia
1> +---
1>B::$vbtable@: //虚基表
1> 0 | 0 // 虚基指针距离派生类对象偏移0B
1> 1 | 8 (Bd(B+0)A) // 虚基指针向下偏移8B找到虚基类

虚继承多一个虚基指针,共12B,虚拟继承会将派生类的逻辑存到前面;

虚基表中存放的内容:(1)虚基指针距离派生类对象首地址的偏移信息(2)虚基类的偏移信息

测试二、单个虚继承,带虚函数

2.1、单个继承,带虚函数
1>class B size(12):
1> +---
1> 0 | +--- (base class A)
1> 0 | | {vfptr} //虚函数指针
1> 4 | | _ia
1> | +---
1> 8 | _ib
1> +---
1>B::$vftable@: //虚表
1> | &B_meta
1> | 0
1> 0 | &B::f // f 和 fb2 入虚表,fb不是虚函数,不入虚表
1> 1 | &B::fb2 // 派生类新增虚函数直接放在基类虚表中

带虚函数的话,多一个虚函数指针,指向虚表,所以共占12B,派生类新增的虚函数放入基类虚表

2.3、单个虚继承,带虚函数,派生类不新增
8/16
1>class B size(16):
1> +---
1> 0 | {vbptr} //有虚继承的时候就多一个虚基指针,虚基指针指向虚基表
1> 4 | _ib //有虚函数的时候就产生一个虚函数指针,虚函数指针指向虚函数表
1> +---
1> +--- (virtual base A)
1> 8 | {vfptr}
1>12 | _ia
1> +---
1>B::$vbtable@: //虚基表
1> 0 | 0 // 虚基指针距离派生类对象偏移0B
1> 1 | 8 (Bd(B+0)A) // 虚基指针向下偏移8B找到虚基类
1>B::$vftable@: //虚函数表
1> | -8
1> 0 | &B::f

两个 int 型变量,一个虚函数指针,一个虚基指针,共占16B;

虚拟继承使得派生类逻辑存在基类前面;

(虚拟继承后,基类在派生类后面,虚函数指针也在下面,派生类要找到虚函数表,向后偏移8B)

2.2 单个虚继承,带虚函数 (自己新增
1>class B size(20):
1> +---
1> 0 | {vfptr} //虚函数指针
1> 4 | {vbptr} //虚基指针 (虚继承多一个) {虚拟继承,派生类在前面}
1> 8 | _ib
1> +---
1> +--- (virtual base A)
1>12 | {vfptr} //虚函数指针
1>16 | _ia
1> +---
1>B::$vftable@B@: //虚表
1> | &B_meta
1> | 0
1> 0 | &B::fb2 //派生类新增虚函数,放在最前面,访问新增虚函数快一些,不用偏移 ,多一个虚函数指针,指向新的虚表
1>B::$vbtable@: //虚基表
1> 0 | -4 //虚基指针距离派生类对象首地址的偏移信息
1> 1 | 8 (Bd(B+4)A) //找到虚基类的偏移信息
1>B::$vftable@A@: //虚表
1> | -12
1> 0 | &B::f 基类布局在最后面

派生类中新增一个虚函数指针,指向一张新的虚表,存放派生类新增的虚函数,可以更快的访问到

所以,两个虚函数指针,一个虚基指针,两个int类型变量,共20B

测试三:多重继承(带虚函数)

3.1、普通多重继承,带虚函数,自己有新增虚函数
28 //Base1中 f() g() h() , Base2中 f() g() h() , Base3中 f() g() h() Derived 中 f() g1()
1>class Derived size(28):
1> +---
1> 0 | +--- (base class Base1) //基类有自己的虚函数表,基类的布局按照被继承时的顺序排列
1> 0 | | {vfptr} // 3个虚函数指针指向不同虚表
1> 4 | | _iBase1
1> | +---
1> 8 | +--- (base class Base2)
1> 8 | | {vfptr}
1>12 | | _iBase2
1> | +---
1>16 | +--- (base class Base3)
1>16 | | {vfptr}
1>20 | | _iBase3
1> | +---
1>24 | _iDerived
1> +---
1>Derived::$vftable@Base1@:
1> | &Derived_meta
1> | 0
1> 0 | &Derived::f(虚函数的覆盖) //第一个虚函数表中存放真实的被覆盖的虚函数地址,其他虚函数表中存放跳转地址
1> 1 | &Base1::g
1> 2 | &Base1::h
1> 3 | &Derived::g1 (新的虚函数,直接放在基类之后,加快查找速度)
1>Derived::$vftable@Base2@:
1> | -8
1> 0 | &thunk: this-=8; goto Derived::f //虚函数表还可以存放跳转指令
1> 1 | &Base2::g
1> 2 | &Base2::h
1>Derived::$vftable@Base3@:
1> | -16
1> 0 | &thunk: this-=16; goto Derived::f
1> 1 | &Base3::g
1> 2 | &Base3::h

Base1、Base2、Base3中各有一个虚函数指针指向自己的虚表,有4个int类型的数据成员,共占28B

第一个虚函数表中存放真实的被覆盖的虚函数地址,其他虚函数表中存放跳转地址

3.2、虚拟多重继承,带虚函数,自己有新增虚函数(只有第一个是虚继承)
32 Base1是虚继承
1>class Derived size(32): //多一个虚基指针
1> +---
1> 0 | +--- (base class Base2)
1> 0 | | {vfptr}
1> 4 | | _iBase2
1> | +---
1> 8 | +--- (base class Base3)
1> 8 | | {vfptr}
1>12 | | _iBase3
1> | +---
1>16 | {vbptr}
1>20 | _iDerived
1> +---
1> +--- (virtual base Base1)
1>24 | {vfptr}
1>28 | _iBase1
1> +---
1>Derived::$vftable@Base2@:
1> | &Derived_meta
1> | 0
1> 0 | &Derived::f //第一个虚函数表中存放真实的被覆盖的虚函数地址,其他虚函数表中存放跳转地址
1> 1 | &Base2::g
1> 2 | &Base2::h
1> 3 | &Derived::g1
1>Derived::$vftable@Base3@:
1> | -8 //去找Derived::f
1> 0 | &thunk: this-=8; goto Derived::f
1> 1 | &Base3::g
1> 2 | &Base3::h
1>Derived::$vbtable@: //虚基表
1> 0 | -16
1> 1 | 8 (Derivedd(Derived+16)Base1)
1>Derived::$vftable@Base1@:
1> | -24
1> 0 | &thunk: this-=24; goto Derived::f
1> 1 | &Base1::g
1> 2 | &Base1::h

虚拟继承会将派生类的逻辑存到前面,Base1是虚继承,所以内存中的存放顺序为 Base2、Base3、Derived、Base1

所占空间大小,在上面一个例子基础上,多一个虚基指针,所以占32B

虚基指针向上偏移16B得到派生类对象首地址,向下偏移8B找到虚基类

3.3、虚拟多重继承,带虚函数,自己有新增虚函数(三个都是虚继承)
36
1>class Derived size(36): //多一张虚表
1> +---
1> 0 | {vfptr} //以空间换时间 新增虚函数,多张虚表
1> 4 | {vbptr}
1> 8 | _iDerived
1> +---
1> +--- (virtual base Base1)
1>12 | {vfptr}
1>16 | _iBase1
1> +---
1> +--- (virtual base Base2)
1>20 | {vfptr}
1>24 | _iBase2
1> +---
1> +--- (virtual base Base3)
1>28 | {vfptr}
1>32 | _iBase3
1> +---
1>Derived::$vftable@Derived@:
1> | &Derived_meta
1> | 0
1> 0 | &Derived::g1
1>Derived::$vbtable@:
1> 0 | -4
1> 1 | 8 (Derivedd(Derived+4)Base1)  //vbptr偏移8B找到虚基类Base1
1> 2 | 16 (Derivedd(Derived+4)Base2) // vbptr偏移16B找到虚基类Base2
1> 3 | 24 (Derivedd(Derived+4)Base3)
1>Derived::$vftable@Base1@:
1> | -12
1> 0 | &Derived::f
1> 1 | &Base1::g
1> 2 | &Base1::h
1>Derived::$vftable@Base2@:
1> | -20
1> 0 | &thunk: this-=8; goto Derived::f
1> 1 | &Base2::g
1> 2 | &Base2::h
1>Derived::$vftable@Base3@:
1> | -28
1> 0 | &thunk: this-=16; goto Derived::f
1> 1 | &Base3::g
1> 2 | &Base3::h

虚拟继承会将派生类的逻辑存到前面,3个Base都是虚继承,所以内存中的存放顺序为Derived、Base1、 Base2、Base3

在上一个例子的基础上,多一张虚表,所以占36B

测试四、菱形虚继承

4.1、菱形普通继承(存储二义性)
48 B1、B2继承B;D继承B1、B2
class D size(48):
1> +---
1> 0 | +--- (base class B1)
1> 0 | | +--- (base class B)
1> 0 | | | {vfptr}
1> 4 | | | _ib //存储二义性
1> 8 | | | _cb //1
1> | | | <alignment member> (size=3) //内存对齐
1> | | +---
1>12 | | _ib1
1>16 | | _cb1
1> | | <alignment member> (size=3)
1> | +---
1>20 | +--- (base class B2)
1>20 | | +--- (base class B)
1>20 | | | {vfptr}
1>24 | | | _ib //存储二义性
1>28 | | | _cb
1> | | | <alignment member> (size=3)
1> | | +---
1>32 | | _ib2
1>36 | | _cb2
1> | | <alignment member> (size=3)
1> | +---
1>40 | _id
1>44 | _cd
1> | <alignment member> (size=3)
1> +---
1>D::$vftable@B1@:
1> | &D_meta
1> | 0
1> 0 | &D::f
1> 1 | &B::Bf
1> 2 | &D::f1
1> 3 | &B1::Bf1
1> 4 | &D::Df
1>D::$vftable@B2@:
1> | -20
1> 0 | &thunk: this-=20; goto D::f
1> 1 | &B::Bf
1> 2 | &D::f2
1> 3 | &B2::Bf2

B的数据成员有两份,造成了存储二义性,共占48B

4.2、菱形虚拟继承        B1、B2虚拟继承B;D普通继承B1、B2
52
1>class D size(52):
1> +---
1> 0 | +--- (base class B1) //基类B1
1> 0 | | {vfptr}
1> 4 | | {vbptr} // +36 找到虚基类
1> 8 | | _ib1
1>12 | | _cb1
1> | | <alignment member> (size=3)
1> | +---
1>16 | +--- (base class B2) //基类B2
1>16 | | {vfptr}
1>20 | | {vbptr} // +20找到虚基类
1>24 | | _ib2
1>28 | | _cb2
1> | | <alignment member> (size=3)
1> | +---
1>32 | _id //派生类D
1>36 | _cd
1> | <alignment member> (size=3)
1> +---
1> +--- (virtual base B) //基类B
1>40 | {vfptr}
1>44 | _ib
1>48 | _cb
1> | <alignment member> (size=3)
1> +---
1>D::$vftable@B1@:
1> | &D_meta
1> | 0
1> 0 | &D::f1 // D中覆盖了
1> 1 | &B1::Bf1 //新增
1> 2 | &D::Df //D中新增,放到B1的虚函数表中
1>D::$vftable@B2@:
1> | -16
1> 0 | &D::f2 // D中覆盖了
1> 1 | &B2::Bf2 //新增
1>D::$vbtable@B1@:
1> 0 | -4 //距离派生类对象B1首地址偏移 -4
1> 1 | 36 (Dd(B1+4)B)
1>D::$vbtable@B2@:
1> 0 | -4 //距离派生类对象B2首地址偏移 -4
1> 1 | 20 (Dd(B2+4)B)
1>D::$vftable@B@:
1> | -40
1> 0 | &D::f
1> 1 | &B::Bf

B1、B2各有虚基指针

存储顺序本来是:派生类B1、基类B、派生类B2、基类B、派生类D

存储顺序:派生类B1、派生类B2、派生类D、基类B(基类放到后面,解决了存储二义性)

C++Day12 虚拟继承内存布局测试的更多相关文章

  1. C++类继承内存布局(一)

    转自:http://blog.csdn.net/jiangyi711/article/details/4890889# 一 类布局 不同的继承方式将导致不同的内存布局 1)C结构 C++基于C,所以C ...

  2. C++类继承内存布局(三)

    参考:http://blog.csdn.net/jiangyi711/article/details/4890889# (三)成员函数 类X中每一个非静态成员函数都会接受一个特殊的隐藏参数——this ...

  3. C++类继承内存布局(二)

    转自:http://blog.csdn.net/jiangyi711/article/details/4890889# (二 )成员变量 前面介绍完了类布局,接下来考虑不同的继承方式下,访问成员变量的 ...

  4. C++继承 派生类中的内存布局(单继承、多继承、虚拟继承)

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

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

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

  6. C++ 各种继承方式的类内存布局

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

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

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

  8. c++,为什么要引入虚拟继承

      虚拟基类是为解决多重继承而出现的.   以下面的一个例子为例: #include <iostream.h> #include <memory.h> class CA { i ...

  9. Vc++内存布局

    Vc++内存布局 测试平台 Windows server 2012 R2 and visual studio 2013 professional. 本篇文章意在介绍vc++中类的内存布局方式,只是研究 ...

  10. 继承虚函数浅谈 c++ 类,继承类,有虚函数的类,虚拟继承的类的内存布局,使用vs2010打印布局结果。

    本文笔者在青岛逛街的时候突然想到的...最近就有想写几篇关于继承虚函数的笔记,所以回家到之后就奋笔疾书的写出来发布了 应用sizeof函数求类巨细这个问题在很多面试,口试题中很轻易考,而涉及到类的时候 ...

随机推荐

  1. JAVA学习前准备

    电脑常用快捷键 Ctrl+C:复制 Ctrl+V:粘贴 Ctrl+A:全选 Ctrl+X:剪切 Ctrl+Z:撤销 Ctrl+S:保存 Alt+F4:关闭窗口 Shift+delete:永久性删除文件 ...

  2. C++11绑定器bind及function机制

    前言 之前在学muduo网络库时,看到陈硕以基于对象编程的方式,大量使用boost库中的bind和function机制,如今,这些概念都已引入至C++11,包含在头文件<functional&g ...

  3. Mysql之MGR高可用实战案例

    MGR高可用实战案例 1.环境准备 node1 rocky8.6 10.0.0.8 node2 rocky8.6 10.0.0.18 node3 rocky8.6 10.0.0.28 2.所有节点更改 ...

  4. Docker基础和常用命令

    Docker基础和常用命令 一,Docker 简介 1.1,什么是 Docker Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的 cgroup,nam ...

  5. Pwn学习随笔

    Pwn题做题流程 使用checksec检查ELF文件保护开启的状态 IDApro逆向分析程序漏洞(逻辑复杂的可以使用动态调试) 编写python的exp脚本进行攻击 (若攻击不成功)进行GDB动态调试 ...

  6. ElasticSearch深度分页详解

    1 前言 ElasticSearch是一个实时的分布式搜索与分析引擎,常用于大量非结构化数据的存储和快速检索场景,具有很强的扩展性.纵使其有诸多优点,在搜索领域远超关系型数据库,但依然存在与关系型数据 ...

  7. SpringBoot3正式版将于11月24日发布:都有哪些新特性?

    从 2018 年 2 月 28 号发布 Spring Boot 2.0 版本开始,整个 2.X 版本已经经过了 4 年多的时间,累计发布了 95 个不同的版本,而就在前不久,2.X 系列的也已经迎来了 ...

  8. React+echarts (echarts-for-react) 画中国地图及省份切换

    有足够的地图数据,可以点击到街道,示例我只出到市级 以umi为框架,版本是: "react": "^18.2.0", "umi": &quo ...

  9. mysql管理工具mysqladmin的使用

    1. 初始化密码 mysqladmin -uroot -p'password' password 'new-password' [root@controller3 ~]# yum -y install ...

  10. php7怎么安装memcache扩展

    php7安装memcache扩展 1.下载文件,解压缩 memcache windows php7下载地址: https://github.com/nono303/PHP7-memcache-dll ...