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,一经查实,立即删除!

相关文章

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;方式不太一样。 还涉及到分账分佣以及跟银行的对接…

大数据-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…

计算机网络基础(1)

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

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

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

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;我也定期回复一些评论一起讨论。以下是我遇到的情…

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

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

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

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

访问控制列表(课内实验)

实验2&#xff1a;访问控制列表 实验目的及要求&#xff1a; 通过实验&#xff0c;进一步的理解标准ACL与扩展ACL的工作原理及执行过程。理解通配符的概念&#xff0c;熟练掌握标准ACL与扩展ACL的配置指令&#xff0c;掌握将访问控制列表应用VTY线路上&#xff0c;并且能够判断…

鸿蒙开发 四十五 鸿蒙状态管理(嵌套对象界面更新)

当运行时的状态变量变化&#xff0c;UI重新渲染&#xff0c;在ArkUI中称为状态管理机制&#xff0c;前提是变量必须被装饰器修饰。不是状态变量的所有更改都会引起刷新&#xff0c;只有可以被框架观测到的更改才会引起UI刷新。其中boolen、string、number类型&#xff0c;可观察…

Oracle漏洞修复 19.3 补丁包 升级为19.22

1.场景描述 上周末2024-10-12日,服务器扫出漏洞,希望及时修复。其中,oracle的漏洞清单如下,总结了下,基本都是 Oracle Database Server 的 19.3 版本到 19.20 版本和 21.3 版本到 21.11 版本存在安全漏洞,即版本问题。如: Oracle Database Server 安全漏洞(CVE-2023-22…

Spring AI Java程序员的AI之Spring AI(一)

SpringAI 基础使用 前言Spring AIChatClientImageClientOpenAiAudioTranscriptionClientEmbeddingClient 总结 前言 Spring AI&#xff0c;听着名字就感觉很好使用&#xff0c;快速上手&#xff0c;虽然功能没有太完善&#xff0c;但是社区活跃度很高&#xff0c;可以看看源码…

低代码框架参考

企业管理信息系统作为一类重要的应用软件系统&#xff0c;具有自己的特点&#xff0c;主要有两个方面&#xff1a; 1. 系统规模大&#xff0c;目前市场上常见的ERP系统一般都有几千个页面。 2. 页面逻辑相似性强。经过比较可以发现&#xff0c;大部分页面具有类似的功能&…

Docker新手必看:快速安装和配置BookStack在线文档系统

文章目录 前言1. 安装Docker2. Docker镜像源添加方法3. 创建并启动BookStack容器4. 登录与简单使用5. 公网远程访问本地BookStack5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定公网地址远程访问 前言 本文主要介绍如何在Linux系统使用Docker本地部署在线文档管理…

【c++篇】:初识c++--编程新手的快速入门之道(二)

文章目录 前言一.引用1.引用的概念2.引用的特性3.引用的使用场景4.常引用5.引用和指针的区别 二.内联函数1.C语言的宏函数2.内联函数的概念3.内联函数的特性 三.auto关键字1.auto的定义2.auto的使用规则3.auto不能推导的场景 四.基于范围的for循环1.范围for的语法2.范围for的使…