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

Java 语言特性(面试系列1)

一、面向对象编程 1. 封装&#xff08;Encapsulation&#xff09; 定义&#xff1a;将数据&#xff08;属性&#xff09;和操作数据的方法绑定在一起&#xff0c;通过访问控制符&#xff08;private、protected、public&#xff09;隐藏内部实现细节。示例&#xff1a; public …...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

visual studio 2022更改主题为深色

visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中&#xff0c;选择 环境 -> 常规 &#xff0c;将其中的颜色主题改成深色 点击确定&#xff0c;更改完成...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

剑指offer20_链表中环的入口节点

链表中环的入口节点 给定一个链表&#xff0c;若其中包含环&#xff0c;则输出环的入口节点。 若其中不包含环&#xff0c;则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

AI,如何重构理解、匹配与决策?

AI 时代&#xff0c;我们如何理解消费&#xff1f; 作者&#xff5c;王彬 封面&#xff5c;Unplash 人们通过信息理解世界。 曾几何时&#xff0c;PC 与移动互联网重塑了人们的购物路径&#xff1a;信息变得唾手可得&#xff0c;商品决策变得高度依赖内容。 但 AI 时代的来…...

Python 包管理器 uv 介绍

Python 包管理器 uv 全面介绍 uv 是由 Astral&#xff08;热门工具 Ruff 的开发者&#xff09;推出的下一代高性能 Python 包管理器和构建工具&#xff0c;用 Rust 编写。它旨在解决传统工具&#xff08;如 pip、virtualenv、pip-tools&#xff09;的性能瓶颈&#xff0c;同时…...

#Uniapp篇:chrome调试unapp适配

chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器&#xff1a;Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...