当前位置: 首页 > news >正文

深度解读《深度探索C++对象模型》之虚继承的实现分析和效率评测(一)

目录

前言 

具有虚基类的对象的构造过程

通过子类的对象存取虚基类成员的实现分析


接下来我将持续更新“深度解读《深度探索C++对象模型》”系列,敬请期待,欢迎左下角点击关注!也可以关注公众号:iShare爱分享,或文章末尾扫描二维码,自动获得推文和全部的文章列表。

前言 

        前面几篇分析了静态数据成员、普通的数据成员以及在继承体系下的数据成员的存取效率的分析,请从这里阅读:

        深度解读《深度探索C++对象模型》之数据成员的存取效率分析(一)

        深度解读《深度探索C++对象模型》之数据成员的存取效率分析(二)

        深度解读《深度探索C++对象模型》之数据成员的存取效率分析(三)

        接下来来分析虚继承的实现以及它的效率评测,在读这篇文章之前,为了能够更好地理解内容,建议先阅读一下以下的文章,补充一些基础知识。

        深度解读《深度探索C++对象模型》之默认构造函数

        深度解读《深度探索C++对象模型》之C++对象的内存布局(一)

        深度解读《深度探索C++对象模型》之C++对象的内存布局(二)

        深度解读《深度探索C++对象模型》之C++虚函数实现分析(一)

        深度解读《深度探索C++对象模型》之C++虚函数实现分析(二)

        深度解读《深度探索C++对象模型》之C++虚函数实现分析(三)

        深度解读《深度探索C++对象模型》之C++虚函数实现分析(四)

        深度解读《深度探索C++对象模型》之C++对象的构造过程(一)

        深度解读《深度探索C++对象模型》之C++对象的构造过程(二)

        深度解读《深度探索C++对象模型》之C++对象的构造过程(三)

        现在来分析在虚继承时访问虚基类的数据成员的实现方法,以及它和访问普通的数据成员之间的效率对比评测。虚继承虽然很少使用,但可能难以避免有时业务中确实需要用到,这时熟悉编译器对于虚继承的实现手法和存取虚基类成员的效率,这样可以对所写的代码了然于胸,做到心中有数。我们以一个具体的例子来分析:

class Grand {
public:virtual ~Grand() {}int g;
};
class Base1: virtual public Grand {
public:int b1;
};
class Base2: virtual public Grand {
public:int b2;
};
class Derived: public Base1, public Base2 {
public:int d;
};int main() {Derived d;d.g = 5;Derived* pd = &d;pd->g = 6;Base1* pb1 = &d;pb1->g = 7;Base2* pb2 = &d;pb2->g = 8;Grand* pg = &d;pg->g = 9;return 0;
}

        要深入分析编译器对虚继承的实现手法,最好的方法是分析编译器生成的汇编代码,上面短短的C++代码生成的汇编代码却相当多,不可能全部贴出来,只能将有需要讲到的地方贴出来。

具有虚基类的对象的构造过程

        首先,main函数的第一行定义了一个Derived类的对象,这里则会去调用Derived类的默认构造函数,在Derived类的构造函数里首先会去调用Grand子类的默认构造函数,然后调用Base1子类和Base2子类的默认构造函数,最后是完成自身的构造。不要奇怪为什么会去调用这些默认构造函数,明明代码中并没有定义这些函数啊,如果对这个有疑问的话可以先看一下另外一篇“深度解读《深度探索C++对象模型》之默认构造函数”。在这些默认构造函数里主要的事情就是去设置虚表指针,因为代码中有虚继承,所以编译器会生成一个虚表,而且虚基类中有定义了虚函数,所以它的派生类中都会继承虚函数(这里指的都是虚析构函数),所以也有一个虚函数表,这些具体的细节不同的编译器有不同的实现手法,clang和gcc是将这两个表合二为一,只需要一个指针指向它们,而MSVC是分开两个表,所以需要两个指针来指向它们,但是原理都大致相同,这里就以clang的实现为例。

        下面是Derived类的构造函数的汇编代码:

        上面汇编代码的前三行是保存上个函数的栈寄存器,然后开辟了16字节的栈空间来使用。接着是将rdi寄存器的值保存到栈空间中,rdi是调用Derived类构造函数时传递过来的参数,它是Derived类的对象d的地址。

        上面汇编代码的第66行,在此地址之上偏移32个字节(跳过Base1子对象和Base2子对象),即为Grand类子对象的起始地址(对对象的内存布局还不熟悉的,可以先参考:

深度解读《深度探索C++对象模型》之C++对象的内存布局(一)

深度解读《深度探索C++对象模型》之C++对象的内存布局(二)

),这时将rdi寄存器(对象d的地址偏移了32字节后)作为参数,调用Grand类的默认构造函数。下面是Grand类的默认构造函数汇编代码:

        汇编代码的第110行到112行,在Grand类的默认构造函数里会先设置Grand类的虚函数表指针(指向Grand类的虚函数表,每个类都会有一个虚函数表),[rip + vtable for Grand]是虚表的地址,以下这个表的内容:

        前面两行先不管它,第三、四行即存放虚函数的地址。所以上面汇编代码的第111行里跳过16字节,即是跳过了前面两行,将第三行的内容即虚函数的地址,设置给Grand类子对象的起始地址中,至此完成了Grand类子对象的虚函数表的设置。

        回到Derived类的构造函数的汇编代码中,见第68行到71行,这里是去调用Base1子类的默认构造函数:

        [rbp - 16] 栈空间保存的是Derived类的对象d的地址,这里再加载到rdi寄存器中,作为调用Base1类默认构造函数的第一个参数。第69行代码是取得“VTT for Derived”表的地址并保存在rsi寄存器中,作为调用Base1类默认构造函数的第二个参数。

        “VTT for Derived”表的内容如下:

        上面汇编代码的第70行将rsi里的值加上8的偏移值,也就是上表的起始地址加上8,实际上就是指向第二条内容的地址,最后第71行代码调用Base1类的默认构造函数。

        Base1类的默认构造函数代码:

        第118行、119行代码将第一个参数rdi寄存器和第二个参数rsi寄存器中的内容分别保存到栈空间[rbp - 8]和[rbp - 16]中。从上面的分析中我们知道,rsi保存的内容是“construction vtable for Base1-in-Derived+24”,它实际上是表“construction vtable for Base1-in-Derived”的起始地址加上偏移值24的意思。那么来看下“construction vtable for Base1-in-Derived”表的内容:

        上面的表加上24的偏移值,实际上就是跳过前面三行的内容,指向第四条的内容,也就是Base1类的虚析构函数的地址。然后上面汇编代码的第122行、123行将这个地址设置给Base1子对象的起始位置,这个就是之前说过的设置虚函数表指针。

        接下来的第124行到127行的代码意思跟前面的差不多,只不过它设置的虚基类子对象的虚函数表指针。第124行的rcx + 8,rcx原先的内容是“VTT for Derived”表的第二行即“construction vtable for Base1-in-Derived+24”,这里再加8就是指向第三行,并将它的内容保存到rdx寄存器中。第126行的rcx - 24,实际上就是跳回到“construction vtable for Base1-in-Derived”表的起始位置,然后对其取值,也就是32(参见上面的表)并保存到rcx寄存器中。在汇编代码的第127行,rax + rcx表示对象d的起始地址(也是Base1子对象的起始地址)加上32的偏移值,定位到虚基类Grand类的子对象的起始地址,并将虚函数表指针设置到这个起始地址中。

        接下来的Base2子对象的构造过程跟构造Base1子对象的过程类似,不同的是设置的虚函数表指针的内容不同。最后是Derived类子对象的构造,过程都大同小异,这里就不再赘述。

        通过上面的分析我们知道,在构造Base1和Base2子类的时候,除了设置Base1和Base2自身的虚函数表指针之外,还会重新设置Grand类的虚函数表指针(设置两次,一次设置为指向Base1类的,后一次设置为指向Base2类的),最后在构造Derived类的时候全都更新为指向Derived类的虚函数表。

        构造完Derived类的对象后,接着来分析存取虚基类的数据成员g,我们采取几种不同的途径来存取,如通过Derived类的对象、Derived类型的指针、Base1和Base2父类的指针以及虚基类Grand类型的指针来存取数据成员g,分别分析它们的实现手法有什么区别。

通过子类的对象存取虚基类成员的实现分析

        首先通过对象来存取,C++代码第21行:d.g = 5;,对应的汇编代码如下:

mov     rax, qword ptr [rbp - 56]
mov     rax, qword ptr [rax - 24]
mov     dword ptr [rbp + rax - 48], 5

        [rbp - 56]是对象Derived对象d的地址,这个地址在构造对象d的最后阶段的时候被写入虚函数表指针:

mov     rax, qword ptr [rbp - 16]       # 8-byte Reload
lea     rcx, [rip + vtable for Derived]
add     rcx, 24
mov     qword ptr [rax], rcx

        第2行是加载虚表的地址到rcx寄存器(这个虚表包含了虚基类表和虚函数表),然后加上偏移值24写入到对象的起始地址中,加上偏移值24后指向了虚函数的地址,下面是Derived类的虚表的内容:

vtable for Derived:.quad   32.quad   0.quad   typeinfo for Derived.quad   Derived::~Derived() [complete object destructor].quad   Derived::~Derived() [deleting destructor].quad   16.quad   -16.quad   typeinfo for Derived.quad   non-virtual thunk to Derived::~Derived() [complete object destructor].quad   non-virtual thunk to Derived::~Derived() [deleting destructor].quad   -32.quad   -32.quad   typeinfo for Derived.quad   virtual thunk to Derived::~Derived() [complete object destructor].quad   virtual thunk to Derived::~Derived() [deleting destructor]

        这个表中有几种类型的虚函数,这个主要是跟多态的调用有关,主要是为了实现虚函数的多态调用,这里先不分析,后面再专门讲这个。接着上面的汇编代码,对象d的起始地址的内容现在就是虚表的地址偏移24字节,rax - 24就相当于又指向了虚表的起始地址,[rax - 24]是取这个地址的内容(相当于指针的解引用),也就是32。rbp + rax - 48相当于rbp - 56 + 8 + rax,rbp - 56是对象的起始地址,加上rax即32,是跳过了Base1和Base2两个子类的大小,再加8是因为Grand子类的前面有一个虚函数表指针,大小为8字节,所以最终指向的地址为数据成员g的地址,然后对其赋值为5。

(未完待续。。。敬请点击左下角的关注以获得及时更新)


本主页会定期更新,为了能够及时获得更新,敬请关注我:点击左下角的关注。也可以关注公众号:请在微信上搜索公众号“iShare爱分享”并关注,或者扫描以下公众号二维码关注,以便在内容更新时直接向您推送。

相关文章:

深度解读《深度探索C++对象模型》之虚继承的实现分析和效率评测(一)

目录 前言 具有虚基类的对象的构造过程 通过子类的对象存取虚基类成员的实现分析 接下来我将持续更新“深度解读《深度探索C对象模型》”系列,敬请期待,欢迎左下角点击关注!也可以关注公众号:iShare爱分享,或文章末…...

计算机Java项目|Springboot房产销售系统

作者主页:编程指南针 作者简介:Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容:Java项目、Python项目、前端项目、人工智能与大数据、简…...

学习3D几何和特征一致的高斯溅射目标去除

earning 3D Geometry and Feature Consistent Gaussian Splatting for Object Removal 学习3D几何和特征一致的高斯溅射目标去除 Yuxin Wang 王玉欣 HKUST &Qianyi Wu Monash University &Guofeng Zhang Zhejiang University &Dan Xu HKUST 香港科技大学&吴倩…...

PHP 使用常量实现枚举类

PHP 使用常量实现枚举类 <?php abstract class Enum {private static $constCacheArray NULL;private static function getConstants() {if (self::$constCacheArray NULL) {self::$constCacheArray [];}$calledClass get_called_class();if (!array_key_exists($call…...

Linux操作系统基础题库

一. 单选题&#xff08;共2题&#xff0c;40分&#xff09; 1. (单选题)Linux操作系统自诞生至今&#xff0c;有数十万的程序开发人员参与到了它的开发与完善中&#xff0c;如今Linux已发展成为是一个成熟、稳定的操作系统。从以下选项中选出关于Linux特点描述完全正确的一项。…...

Java抽象类:为何它是你代码架构的基石?

目录 1、抽象类的概念 2、抽象类语法 3、抽象类特性 4、抽象类的作用 5、 完结散花 个人主页&#xff1a;秋风起&#xff0c;再归来~ 文章专栏&#xff1a;javaSE的修炼之路 个人格言&#xff1a;悟已往之不谏&#xff0c;知来者犹可追 克…...

Flutter 中的 ToggleButtons 小部件:全面指南

Flutter 中的 ToggleButtons 小部件&#xff1a;全面指南 在 Flutter 中&#xff0c;ToggleButtons 是一种允许用户在一组选项中进行切换选择的控件。它通常用于展示一组相关选项&#xff0c;让用户可以快速切换选择。ToggleButtons 是一种水平排列的按钮集合&#xff0c;其中…...

【MYSQL】一颗B+树可以保存多少条数据

引言 事万物都有自己的单元体系&#xff0c;若干个小单体组成一个个大的个体。就像拼乐高一样&#xff0c;可以自由组合。所以说&#xff0c;如果能熟悉最小单元&#xff0c;就意味着我们抓住了事物的本事&#xff0c;再复杂的问题也会迎刃而解。 存储单元 存储器范围比较大…...

ssm125四六级报名与成绩查询系统+jsp

四六级报名与成绩查询系统的设计与实现 摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对四六级报名信息管理混乱&am…...

【Unity从零开始学习制作手机游戏】第01节:控制3D胶囊体运动

1. 新建Project L01 使用3D Mobile模板。 2. 建立一个平面&#xff0c;用来承载物体 3. 导入Unity库内的胶囊体 下载 StandardAssets https://download.unitychina.cn/download_unity/e80cc3114ac1/WindowsStandardAssetsInstaller/UnityStandardAssetsSetup-5.6.7f1.exe …...

内容安全(DPI和DFI解析)

内容安全前言&#xff1a; 防火墙的本质其实就是包过滤&#xff0c;我们通常所说的安全设备&#xff08;如&#xff1a;IPS、IDS、AV、WAF&#xff09;的检测重心是应用层。下一代防火墙基于传统防火墙的拓展能力&#xff0c;就是可以将以上的安全设备模块集成在一起&#xff0…...

2024数维杯数学建模A题B题C题思路+模型+代码(开赛后第一时间更新)

2024数维杯数学建模A题B题C题思路模型代码&#xff08;开赛后第一时间更新&#xff09; https://mbd.pub/o/bread/ZpWakpdq https://mbd.pub/o/bread/ZpWakpdq 2024年第九届数维杯大学生数学建模挑战赛参赛规则 竞赛要求及论文提交方式; ①本次参赛作品统一在线提交到竞赛…...

SpringSecurity多表,多端账户登录

本文章对应视频SpringSecurity6多端账号登录&#xff0c;可无限扩展教程&#xff0c;记得三连哦&#xff0c;这对我很重要呢&#xff01; 温馨提示&#xff1a;视频与文章相辅相成&#xff0c;结合学习效果更强哦&#xff01;更多视频教程可移步B站【石添的编程哲学】 SpringSe…...

绝地求生PUBG新老艾伦格有什么差别 老艾伦格什么时候回归

复古风格的艾伦格原始地图携带着那些标志性的记忆符号华丽回归&#xff0c;邀请您沉浸于往昔的每一处细节探索中。我们不仅还原了游戏诞生的起点&#xff0c;还在其中巧妙融入现代游戏元素&#xff0c;构筑一座连接昔日与今朝的桥梁&#xff0c;完美融合了经典与创新的游戏体验…...

Windows下安装Node.js、npm和electronic,并运行一个Hello, World!脚本程序

20240510 By wdhuag 目录 简介&#xff1a; 参考&#xff1a; 安装Node.js 安装npm 配置npm&#xff1a; 修改包存放目录和缓存目录 切换镜像源 使用 nrm 切换镜像源 安装Electron 运行一个Hello, World!脚本程序 安装Yarn JavaScript 指南 简介&#xff1a; Nod…...

【精品案例】化工炼化企业信息化建设解决方案(74页PPT)

一、资料介绍 化工炼化企业信息化建设解决方案是一份详尽且全面的指导文件&#xff0c;旨在助力化工炼化企业实现信息化、智能化和数字化转型。本资料以74页的PPT形式呈现&#xff0c;围绕智能化工程施工方案、化工炼化企业信息化以及化工行业数字化转型等关键词&#xff0c;为…...

【Unity Animation 2D】Unity Animation 2D骨骼绑定与动画制作

一、图片格式为png格式&#xff0c;并且角色各部分分离 图片参数设置 需要将Sprite Mode设置为Single&#xff0c;否则图片不能作为一个整体 1、创建骨骼 1.1 旋转Create Bone&#xff0c;点击鼠标左键确定骨骼位置&#xff0c;移动鼠标再次点击鼠标左键确定骨骼&#xff0c…...

工器具管理(基于若依)

文章目录 前言一、工器具管理项目总览 二、入库功能1. 前端1.1 界面展示1.2 具体操作实现1.3 js文件 2. 后端2.1 工器具信息回显2.2 工器具入库 三、领用功能1. 前端1.1 界面展示1.2 具体实现操作1.3 js文件 2. 后端2.1 工器具信息回显2.2 工器具领用 遇到的问题1. 同一页面展示…...

UE4_照亮环境_光束light beam

学习笔记&#xff0c;不喜勿喷&#xff0c;侵权立删&#xff01;祝愿生活越来越好&#xff01; 光束&#xff1a;模拟大气中散射的光线。利用定向光源模拟真实曙暮光效果或大气散射的阴影&#xff0c;即可生成 光束 。这些光线为场景添加深度和真实度。 一&#xff1a;一些参数…...

springboot3项目练习详细步骤(第三部分:文章管理模块)

目录 发布文章 接口文档 业务实现 自定义参数校验 项目参数要求 实现思路 实现步骤 文章列表(条件分页) 接口文档 业务实现 mapper映射 更新文章 接口文档 业务实现 获取文章详情 接口文档 业务实现 删除文章 接口文档 业务实现 文章管理业务表结构…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…...

HBuilderX安装(uni-app和小程序开发)

下载HBuilderX 访问官方网站&#xff1a;https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本&#xff1a; Windows版&#xff08;推荐下载标准版&#xff09; Windows系统安装步骤 运行安装程序&#xff1a; 双击下载的.exe安装文件 如果出现安全提示&…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域&#xff0c;高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表&#xff0c;以及基于它们实现的 Reactor 模式&#xff0c;为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...

大学生职业发展与就业创业指导教学评价

这里是引用 作为软工2203/2204班的学生&#xff0c;我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要&#xff0c;而您认真负责的教学态度&#xff0c;让课程的每一部分都充满了实用价值。 尤其让我…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

docker 部署发现spring.profiles.active 问题

报错&#xff1a; org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...