实测C++虚函数与内存布局(完整源码)

         C++虚函数究竟是如何实现的?有虚函数的对象的内存结构是什么样的?写几行代码测试一下就很容易理解了。

目录

一、测试代码

二、运行测试

三、分析结果

四、结论


一、测试代码

        首先用VS2022建立一个C++控制台项目(或者随便什么C++项目,别的VS版本当然也没问题),添加下面的代码:

class A
{
public:long long a = 3;virtual void f1() {}virtual void f2() {}
};
class A2
{
public:long long a = 4;virtual void f1() {}virtual void f2() {}
};
class B :public A,public A2
{
public:long long b = 5;virtual void f1() {}virtual void f3() {}
};
void test()
{A a;A2 a2;B b;A* pA = &b;A2* pA2 = &b;int aa = sizeof(A);int bb = sizeof(B);b.b += 1;
}

            解释一下代码:两个基类A和A2,各有两个虚函数和一个成员变量,成员变量有特定初值,方便确认内存数据。子类B,多继承自A和A2,同时有一个新增的虚函数。

二、运行测试

        在main函数开始处调用test()函数,在test函数最后一句设置断点,然后调试执行,见下图:

       图中下部是监视窗口,悬浮部分为内存窗口,在监视窗口选择一行,点右键,复制数据,贴到内存窗口,删掉多余部分,回车,即可显示指定的内存,拉伸内存窗口,使窗口刚好每行16个字节。

三、分析结果

        鼠标移到变量aa和bb上面显示A的大小为16字节、B的大小为40字节,这是因为A包含一个虚表指针和一个成员(long long为8字节),而B包含两个虚表指针(为什么是两个后面分析)和三个成员。

        观察内存窗口的数据(从&a开始显示),第一行是变量A a,后面全cc的是debug版的填充行,方便检测内存越界的,第四行是变量A2 a2,这两个变量的结构都是8字节虚表指针和8字节数据,预设的特征值3、4下面画了红线,第七行开始是变量B b,先是一个A,再是一个A2,第九行开始是B增加的变量,预设值是5。

        现在我们观察B的数据,第七行和第八行开始处分别是父对象A和A2的虚表指针,注意它们和独立定义的A和A2对象(第一行和第四行)的虚表指针完全不同,而第八行A2的虚表指针比第七行A的虚表指针大16,刚好是两个指针长度,这说明每种类型都有自己的虚表(为什么不是每个对象?后面解释),对象的父对象的虚表指针指向虚表的某个位置,对象的第一个虚表指针同时也是第一个父对象的虚表指针。

        修改上面的代码,增加一个独立的A类型的变量,观察内存会发现两个独立的A对象的虚表指针相同,说明虚表是类型共同所有的,注意,是类型,而不是类型中包含的继承部分,示例代码中的A、A2和B是类型,B类型对象继承的A的虚表指针和独立A类型的虚表指针不相同(所以才能实现虚函数指针调用子类的虚函数实现)。

四、结论

        含有虚函数的对象拥有多个虚表指针,整个类型则共享一个虚表,虚表指针计算在sizeof里,虚表则不在里面。虚表指针的数量与基类数量有关,但计算上比较麻烦,因为第一个虚表指针同时也是第一个有虚函数的父类的虚表指针,有很多继承层级和多继承的时候很复杂。

        对象的每个有虚函数的父类都有一个虚表指针,指向对象的虚表的某个部分,如果父类没有虚函数怎么办?修改上面的代码,删掉父类A的虚函数,会发现子类B的布局变成了“A2-A-B”,也就是说,为了节省内存,有虚函数的父类被放在了前面,保证对象和第一个有虚函数的父类共享第一个虚表指针。

        现在很容易理解,从子类指针到父类指针是如何转换的,以及,虚函数调用是如何实现的。

        另:C++对象的内存布局是编译器决定的,没有标准,以上只代表某种情形。

(这里是文档结束)

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

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

相关文章

2024考研计算机考研复试-每日重点(第二十期)

公众号“准研计算机复试”,超全大佬复试资料,保姆级复试,80%的题目都是上岸大佬提供的。 研宝们,App更新啦! 计算机组成原理: 10.☆什么是数据存储的大端模式和小端模式? 大端模式:数…

如何从 Mac 电脑外部硬盘恢复删除的数据文件

本文向您介绍一些恢复 Mac 外置硬盘数据的快速简便的方法。 Mac 的内部存储空间通常不足以存储所有数据。因此,许多用户通过外部驱动器扩展存储或创建数据备份。然而,与几乎所有其他设备一样,从外部硬盘驱动器丢失有价值的数据并不罕见。由于…

基于SpringBoot+MYSQL+Vue的校园管理系统

目录 1、前言介绍 2、主要技术 3、系统流程分析 3.1、操作流程 3.2、添加信息流程 3.3、删除信息流程 4、系统设计 4.1 系统体系结构 4.2开发流程设计 4.3 数据库设计原则 4.4 数据表 5、运行截图(部分) 5.1管理员功能模块 5.2用户功能模块 5.3院校管理员功能模块…

群晖 Synology Photos DSM7 自定义文件夹管理照片

背景 众所周知,目前群晖DSM7中使用Synology Photos做照片管理时,个人照片只能默认索引 /home/Photos 文件夹,但是如果个人照片很多或者用户很多时,共享文件夹/homes 所在的存储空间就会不够用 当然,如果你的存…

【python】anaconda安装过程

【运行环境】Windows11 文章目录 一、anaconda下载二、anaconda安装三、环境变量配置四、测试环境变量是否配置成功五、总结 一、anaconda下载 1、输入网址“https://www.anaconda.com”进入Anaconda官网。 2、找到【Free Download】点击进入: 3、点击对应系统的…

蓝桥杯-质因数问题

约数,又称因数:a % b 0,则b称为a的约数,包括1和a。 例如4的正约数有:1、2、4。6的正约数有:1、2、3、6。质因数: 质因数(素因数或质因子)在数论里是指能整除给定正整数(…

从零开始学习深度学习库-2:反向传播

欢迎来到本系列的第二篇文章,我们将从头开始构建一个深度学习库。 本博客系列的代码可以在这个Github仓库中找到。 上一篇文章 在上一篇文章中(链接见这里),我们实现了线性层和常见的激活函数,并成功构建了神经网络的…

【C语言步行梯】C语言实现三子棋游戏(含详细分析)

🎯每日努力一点点,技术进步看得见 🏠专栏介绍:【C语言步行梯】专栏用于介绍C语言相关内容,每篇文章将通过图片代码片段网络相关题目的方式编写,欢迎订阅~~ 文章目录 需求分析具体实现主函数体菜单实现游戏实…

高级JAVA工程师解决生产环境JVM宕机Java进程挡掉操作系统内存异常实例讲解

高级JAVA工程师解决生产环境JVM宕机Java进程挡掉内存溢出实例讲解 一、事故描述 生产环境Java进程莫名挡掉,JVM宕机。监控平台报警。生产停了,老板急了,客户爆了,怎么迅速解决事故?每次出现生产事故,都是…

IT廉连看——Uniapp——模板语法

IT廉连看——Uniapp——模板语法 众所周知,Uniapp是使用vue.js框架开发出来的,所以说它可以使用vue中的语法和指令来开发我们的项目。如果没有学习过vue的话开发起来会比较吃力,所以这节课就带大家学习几个常用的vue知识。如果有学习过vue&a…

【五、接口自动化测试】

大家好,我是山茶,一个探索AI 测试的程序员 在网上看到了许多关于post与get之间区别的帖子,也有很多帖子是直接粘贴复制的,甚至连标题、符号都没改,甚至还有很多争议 一、post、get 关于post与get之间区别,…

BigDL-LLM 安装指南——在iGPU集成显卡下使用BigDL-LLM大模型库加速LLM

文章目录 iGPU是什么?一、环境准备1.1 Visual Studio 2022 Community 安装1.2 安装或更新最新版本的GPU驱动程序1.3 安装英特尔oneAPI工具包2024.0版本1.4 安装Anaconda 二、BigDL -LLM 安装2.1 创建虚拟环境2.2 激活虚拟环境2.3 安装bigdl-llm[xpu] 三、运行环境配…