当前位置: 首页 > 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映射 更新文章 接口文档 业务实现 获取文章详情 接口文档 业务实现 删除文章 接口文档 业务实现 文章管理业务表结构…...

Oracle查询表空间大小

1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建

华为云FlexusDeepSeek征文&#xff5c;DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色&#xff0c;华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型&#xff0c;能助力我们轻松驾驭 DeepSeek-V3/R1&#xff0c;本文中将分享如何…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...

【笔记】WSL 中 Rust 安装与测试完整记录

#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统&#xff1a;Ubuntu 24.04 LTS (WSL2)架构&#xff1a;x86_64 (GNU/Linux)Rust 版本&#xff1a;rustc 1.87.0 (2025-05-09)Cargo 版本&#xff1a;cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...

FFmpeg:Windows系统小白安装及其使用

一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】&#xff0c;注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录&#xff08;即exe所在文件夹&#xff09;加入系统变量…...

android RelativeLayout布局

<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...