C++ 内存布局 - Part6: 虚继承

1. 关于虚继承

虚继承可以在菱形继承体系中,防止派生类中有多份重复祖基类内容。如下图所示,如果是常规继承,Class Final中会有两份Class Base的内容。通过虚继承,即Derived1 虚继承自Base, Derived2 也虚继承自Base, 那么Final中将最终保留一份Base部分的内容。

2. 代码示例

#include <iostream>class Base {
public:virtual void show() {std::cout << "Base::show() called" << std::endl;}int a = 1;
};class Derived1 : virtual public Base {
public:void show() override {std::cout << "Derived1::show() called" << std::endl;}int b = 2;
};class Derived2 : virtual public Base {
public:void show() override {std::cout << "Derived2::show() called" << std::endl;}int c = 3;
};class Final : public Derived1, public Derived2 {
public:void show() override {std::cout << "Final::show() called" << std::endl;}int d = 4;
};int main() {std::cout << "Base size: " << sizeof(Base) << std::endl;std::cout << "Derived1 size: " << sizeof(Derived1) << std::endl;std::cout << "Derived2 size: " << sizeof(Derived2) << std::endl;std::cout << "Final size: " << sizeof(Final) << std::endl;Derived1 d1;std::cout << "d1 addr: " << &d1 << std::endl;Base *bptr = &d1;std::cout << "bptr: " << bptr << std::endl;bptr->show();Final obj;std::cout << "final obj  addr: " << &obj << std::endl;Derived1 *d1_ptr = &obj;std::cout << "d1_ptr: " << d1_ptr << std::endl;d1_ptr->show();Derived2 *d2_ptr = &obj;std::cout << "d2_ptr: " << d2_ptr << std::endl;d2_ptr->show();Base *b_ptr = &obj;std::cout << "b_ptr: " << b_ptr << std::endl;b_ptr->show();return 0;
}

运行结果:

Base size: 16
Derived1 size: 32
Derived2 size: 32
Final size: 48
d1 addr: 0x7fffffffe240
bptr: 0x7fffffffe250
Derived1::show() called
final obj  addr: 0x7fffffffe210
d1_ptr: 0x7fffffffe210
Final::show() called
d2_ptr: 0x7fffffffe220
Final::show() called
b_ptr: 0x7fffffffe230
Final::show() called

3. Derived1 对象的内存布局

3.1 查看 Derived1对象的内存

(gdb) p d1
$1 = {<Base> = {_vptr.Base = 0x401158 <vtable for Derived1+56>, a = 1}, _vptr.Derived1 = 0x401138 <vtable for Derived1+24>, b = 2}

注意,虽然Base部分写在了前面,但是对于虚继承,实际内存中Base被放在了后面:

(gdb) x/8x &d1
0x7fffffffe240: 0x00401138      0x00000000      0x00000002      0x00000000
0x7fffffffe250: 0x00401158      0x00000000      0x00000001      0x00000000

可以看到,对象的开头内容是 0x00401138, 也就是_vptr.Derived1 = 0x401138 <vtable for Derived1+24>, 紧接着是Derived1的int b, 然后才是_vptr.Base = 0x401158 <vtable for Derived1+56>,然后是Base的数据成员int a.

3.2 查看虚表

1) 首先查看_vptr.Derived1 = 0x401138 <vtable for Derived1+24>

(gdb) x/x 0x401138
0x401138 <vtable for Derived1+24>:      0x00400c7c
(gdb) x/i 0x00400c7c0x400c7c <Derived1::show()>: push   %rbp

可见就是Derived1重写的show()

2) 继续查看 _vptr.Base = 0x401158 <vtable for Derived1+56>

(gdb) x/x 0x401158
0x401158 <vtable for Derived1+56>:      0x00400ca7
(gdb) x/i 0x00400ca70x400ca7 <virtual thunk to Derived1::show()>:        mov    (%rdi),%r10
(gdb) disass 0x00400ca7,+10
Dump of assembler code from 0x400ca7 to 0x400cb1:0x0000000000400ca7 <virtual thunk to Derived1::show()+0>:    mov    (%rdi),%r100x0000000000400caa <virtual thunk to Derived1::show()+3>:    add    -0x18(%r10),%rdi0x0000000000400cae <virtual thunk to Derived1::show()+7>:    jmp    0x400c7c <Derived1::show()>0x0000000000400cb0 <Derived2::show()+0>:     push   %rbp
End of assembler dump.

可以看到,虚表中存放的是thunk代码的地址。

这段thunk代码的用途:当通过基类指针指向Derived1对象时,即Base *bptr = &d1, 由于当前的bptr指向的是Derived1对象中的Base部分(此部分位于对象靠后的位置),而非Derived1对象的真正起始地址,因此通过bptr执行虚函数时,为了执行真正的Derived1中重写的虚函数Derived1::show(),需要调整this指针到Derived1对象的起始地址。

mov    (%rdi),%r10    # rdi中存放的是this指针,也就是指向Base part的指针,而非Derived对象的起始地址, 此操作将this的开头虚表地址条目也就是_vptr.Base = 0x401158 <vtable for Derived1+56> 放入r10

add    -0x18(%r10),%rdi  #  将_vptr.Base的虚表地址-0x18 也就是前移3个条目获取里面存放的offset, 然后将此offset 加到rdi, 执行此操作之前rdi是0x7fffffffe250, offset是0xfffffffffffffff0xf

执行add操作以后,可以得到Derived1对象的起始地址: 0x7fffffffe250 + 0xfffffffffffffff0 = 0x7fffffffe240

(gdb) x/2x 0x401158 - 0x18
0x401140 <vtable for Derived1+32>:      0xfffffff0      0xffffffff

3.3 Derived1 内存布局

 其中虚表中thunk地址前面的条目0x00401200 指向typeinfo

(gdb) x/x 0x00401200
0x401200 <typeinfo for Derived1>:       0x00601d98

4. Final 对象的内存布局

4.1 查看Final对象内存

(gdb) p obj
$22 = {<Derived1> = {<Base> = {_vptr.Base = 0x401060 <vtable for Final+88>, a = 1}, _vptr.Derived1 = 0x401020 <vtable for Final+24>,b = 2}, <Derived2> = {_vptr.Derived2 = 0x401040 <vtable for Final+56>, c = 3}, d = 4}

同样,这只是逻辑视图,而不是内存实际布局。

查看实际内存布局:

(gdb) x/12x &obj
0x7fffffffe210: 0x00401020      0x00000000      0x00000002      0x00000000
0x7fffffffe220: 0x00401040      0x00000000      0x00000003      0x00000004
0x7fffffffe230: 0x00401060      0x00000000      0x00000001      0x00000000

顺序依次是Derived1 part, Derived2 part, Base part

4.2 查看虚表

1)首先查看_vptr.Derived1 = 0x401020 <vtable for Final+24>

(gdb) x/32x 0x401020
0x401020 <vtable for Final+24>: 0x00400ce4      0x00000000      0x00000010      0x00000000
0x401030 <vtable for Final+40>: 0xfffffff0      0xffffffff      0x00401188      0x00000000
0x401040 <vtable for Final+56>: 0x00400d18      0x00000000      0xffffffe0      0xffffffff
0x401050 <vtable for Final+72>: 0xffffffe0      0xffffffff      0x00401188      0x00000000
0x401060 <vtable for Final+88>: 0x00400d0f      0x00000000      0x00401020      0x00000000
0x401070 <VTT for Final+8>:     0x004010b8      0x00000000      0x004010d8      0x00000000
0x401080 <VTT for Final+24>:    0x004010f8      0x00000000      0x00401118      0x00000000
0x401090 <VTT for Final+40>:    0x00401060      0x00000000      0x00401040      0x00000000
(gdb) x/i 0x00400ce40x400ce4 <Final::show()>:    push   %rbp

可见虚表中的虚函数地址就是<Final::show()>

2) 继续查看_vptr.Derived2 = 0x401040 <vtable for Final+56>

(gdb) x/x 0x401040
0x401040 <vtable for Final+56>: 0x00400d18
(gdb) x/i 0x00400d180x400d18 <non-virtual thunk to Final::show()>:       sub    $0x10,%rdi
(gdb) disass 0x00400d18,+10
Dump of assembler code from 0x400d18 to 0x400d22:0x0000000000400d18 <non-virtual thunk to Final::show()+0>:   sub    $0x10,%rdi0x0000000000400d1c <non-virtual thunk to Final::show()+4>:   jmp    0x400ce4 <Final::show()>End of assembler dump.

可以看到,当使用Derived2 *d2_ptr = &obj 来调用虚函数d2_ptr->show();时,需要调整this 指针,但是这个调整比较简单,就是用当前指向Derived2 part的this指针减去0x10

3) 继续查看_vptr.Base = 0x401060 <vtable for Final+88>

(gdb) x/x 0x401060
0x401060 <vtable for Final+88>: 0x00400d0f
(gdb) x/i 0x00400d0f0x400d0f <virtual thunk to Final::show()>:   mov    (%rdi),%r10
(gdb) disass 0x00400d0f,+10
Dump of assembler code from 0x400d0f to 0x400d19:0x0000000000400d0f <virtual thunk to Final::show()+0>:       mov    (%rdi),%r100x0000000000400d12 <virtual thunk to Final::show()+3>:       add    -0x18(%r10),%rdi0x0000000000400d16 <virtual thunk to Final::show()+7>:       jmp    0x400ce4 <Final::show()>

这个就类似于上面讨论Derived1内存布局时通过基类指针操作虚函数时的this指针调整。

4.3 Final 内存布局

其中虚表中thunk地址前面的条目0x00401188指向typeinfo :

(gdb) x/i 0x00401188
   0x401188 <typeinfo for Final>:       cwtl

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/55446.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Gin】Gin框架介绍和使用

一、简单使用Gin框架搭建一个服务器 package mainimport ("github.com/gin-gonic/gin" )func main() {// 创建一个默认的路由引擎r : gin.Default()// GET 请求方法r.GET("/hello", func(c *gin.Context) {// c.JSON 返回的是JSON格式的数据c.JSON(200, g…

003_ipc概述及信号

【背景】 程序运行起来后&#xff0c;每个模块都有自己的进程&#xff0c;那么不同的模块如何进行通讯或者数据交换呢&#xff1f; 上面这张图说明了linux的ipc是继承最初的Unix 的IPC逻辑的&#xff0c;那么具体关系和概述讲解&#xff0c;请参考此链接的原文&#xff1a;htt…

mac 桌面版docker no space left on device

报错信息 docker pull镜像时报&#xff1a; failed to register layer: Error processing tar file(exit status 1): write /home/admin/oceanbase_bak/bin/observer: no space left on device 解决 增加 docker 虚拟磁盘大小。 调整完点击重启即可。

助力语音技术发展,景联文科技提供语音数据采集服务

语音数据采集是语音识别技术、语音合成技术以及其他语音相关应用的重要基础。采集高质量的语音数据有助于提高语音识别的准确性&#xff0c;同时也能够促进语音技术的发展。 景联文科技作为专业的数据采集标注公司&#xff0c;支持语音数据采集。可通过手机、专业麦克风阵列、专…

两个案例全面阐述全链路测试怎么做

首先我们先针对全链路功能测试部分进行一下讲解。去年的时候&#xff0c;有一家电商公司可能知道我一直在帮银行做相关的测试&#xff0c;就请我帮他们去做一些规划。这个平台有虚拟订单&#xff0c;也有实体订单&#xff0c;方式不太一样。 还涉及到分账分佣以及跟银行的对接…

nuxt3脚手架安装报错解决方法

当使用脚手架安装时 npx nuxilatest init appname 错误1&#xff1a;ECONNRESET npm error code ECONNRESET npm error syscall read npm error errno -4077 npm error network read ECONNRESET npm error network This is a problem related to network connectivity. npm …

Redis 集群:高效缓存与数据存储的利器

在当今的互联网时代&#xff0c;数据的存储和处理速度至关重要。Redis 作为一种高性能的内存数据库&#xff0c;广泛应用于各种场景。而 Redis 集群则进一步提升了 Redis 的可用性、扩展性和性能。本文将为你详细介绍 Redis 集群的简介以及三种模式。 一、Redis 集群简介 Redi…

大数据-174 Elasticsearch Query DSL - 全文检索 full-text query 匹配、短语、多字段 详细操作

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

阿里云国际站DDoS高防增值服务怎么样?

利用国外服务器建站的话&#xff0c;选择就具有多样性了&#xff0c;相较于我们常见的阿里云和腾讯云&#xff0c;国外的大厂商还有谷歌云&#xff0c;微软云&#xff0c;亚马逊云等&#xff0c;但是较之这些&#xff0c;同等产品进行比较的话&#xff0c;阿里云可以说当之无愧…

计算机网络基础(1)

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 计算机网络基础 收录于专栏【计算机网络】 本专栏旨在分享学习计算机网络的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 1. 计算机网…

简易CPU设计入门:验证取指令模块【未完成】

项目代码下载 还是请大家首先准备好本项目所用的源代码。如果已经下载了&#xff0c;那就不用重复下载了。如果还没有下载&#xff0c;那么&#xff0c;请大家点击下方链接&#xff0c;来了解下载本项目的CPU源代码的方法。 下载本项目代码 准备好了项目源代码以后&#xff…

linux下详细安装docker

对于有在mac下的docker安装配置请参考mac下安装docker详细教程&#xff0c;在Linux上安装Docker相对简单&#xff0c;以下是详细步骤。以Ubuntu和CentOS为例&#xff0c;步骤略有不同&#xff0c;但基本思想相似。 1. 系统要求 确保你的系统满足以下要求&#xff1a; 64位操…

MySQL详细学习攻略 MySQL基础非常全面教程 MySQL安装教程

MySQL安装教程 章节目录 一、MySQL简介与安装前准备 二、Windows系统下MySQL的安装 三、Linux系统下MySQL的安装 四、MySQL安装后的基本配置 五、MySQL服务的启动与停止 六、MySQL客户端工具的使用 七、MySQL安装常见问题与解决方案 一、MySQL简介与安装前准备 重点内容知识…

Flythings学习(四)串口通信

文章目录 1 串口编程基本步骤1.1 打开串口1.2 配置串口 1.3 读串口1.4 发送串口1.5 关闭串口 2 综合使用3 如何在软件上保证串口稳定通信4 flythings中的串口通讯5 协议接收部分使用和修改方法6 通讯协议数据怎么和UI控件对接 1 串口编程基本步骤 串口通信有5个步骤 1.打开串口…

YOLOv11模型改进-注意力机制-引入自适应稀疏自注意力ASSA

随着目标检测领域的快速发展&#xff0c;YOLO系列模型凭借其端到端、高效的检测性能逐渐成为工业界和学术界的标杆。然而&#xff0c;如何进一步优化YOLOv11的特征提取能力&#xff0c;减少冗余信息并提升模型对复杂场景的适应性&#xff0c;仍是一个值得深入探讨的问题。为此&…

Android:记录一个打包发布版的release包以后闪退的问题

个人感觉其实release闪退的问题挺难排查的&#xff0c;因为release包运行起来as捕获不到相应的应用程序进程&#xff0c;从而不易查看到日志&#xff0c;也是我玩得不溜&#xff0c;大家有不同的方法可以评论区探讨&#xff0c;我也定期回复一些评论一起讨论。以下是我遇到的情…

弧度和角度

弧度和角度是两种测量角度的方式&#xff0c;它们之间可以通过简单的数学公式进行转换。 1. 弧度和角度的定义 角度&#xff08;度数&#xff09;&#xff1a;我们平时最常用的测量角度的单位是角度&#xff0c;通常用**度&#xff08;&#xff09;**表示。一个完整的圆周为 …

【数据结构】宜宾大学-计院-实验三

线性表的应用——实现两多项式的相加 课前准备&#xff1a;实验学时&#xff1a;2实验目的&#xff1a;实验内容&#xff1a;实验结果&#xff1a;实验报告:&#xff08;及时撰写实验报告&#xff09;实验测试结果&#xff1a;代码实现&#xff1a;&#xff08;C/C&#xff09;…

ECharts 实现气象数据可视化的教程

ECharts 是一个由百度开源的强大的数据可视化库&#xff0c;它能够通过简单的配置和灵活的扩展生成交互式图表。对于气象数据的可视化&#xff0c;ECharts 提供了丰富的图表类型&#xff0c;如热力图、雷达图、折线图、风场图等&#xff0c;适用于展示温度、降雨量、风速、风向…

安宝特方案 | AR技术在轨交行业的应用优势

随着轨道交通行业不断向智能化和数字化转型&#xff0c;传统巡检方式的局限性日益凸显。而安宝特AR眼镜以其独特的佩戴方式和轻便设计&#xff0c;为轨道交通巡检领域注入了创新活力&#xff0c;提供了全新的解决方案。 01 多样化佩戴方法&#xff0c;完美适应户外环境 安宝特…