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

news/2024/7/7 21:22:53

         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://lihuaxi.xjx100.cn/news/2092900.html

相关文章

在centOS服务器安装docker,并使用docker配置nacos

遇到安装慢的情况可以优先选择阿里镜像 安装docker 更新yum版本 yum update安装所需软件包 yum install -y yum-utils device-mapper-persistent-data lvm2添加Docker仓库 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.rep…

吴恩达机器学习-可选的实验室-正则化成本和梯度(Regularized Cost and Gradient)

文章目录 目标添加正则化正则化代价函数正则化梯度下降重新运行过拟合示例恭喜 目标 在本实验中,你将: 用正则化项扩展前面的线性和逻辑代价函数。重新运行前面添加正则化项的过拟合示例。 import numpy as np %matplotlib widget import matplotlib.pyplot as p…

Spring Security 结合 JWT使用

Spring Security原理 Spring Security的原理主要基于过滤器链的概念。在Web应用程序中,每个请求都会通过一系列的过滤器,Spring Security就是在这个过程中介入并进行安全相关的操作。 Spring Security的核心原理可以概括为以下几点: 1. 认…

【LeetCode热题100】73. 矩阵置零(矩阵)

一.题目要求 给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 二.题目难度 中等 三.输入样例 示例 1: 输入:matrix [[1,1,1],[1,0,1],[1,1,1]] 输出:[[1,0…

PDF24 Creator PDF工具箱 v11.17.0

软件介绍 可将大部分文件转成pdf格式的免费软件,安装好后会在你的打印机里看到一个叫PDF24的虚拟打印机,你可将要转成pdf格式的文件打印时选虚拟打印机PDF24,也可以直接将文件以拖拉方式拉进这软件的主视窗编辑区里,它会自动转成…

XIAO ESP32S3部署Edge Impulse模型

在上一篇文章中我们介绍了如何使用edge impulse训练一个图片分类模型并导出arduino库文件。在这篇文章中我们将介绍如何在esp32s3中部署这个训练好的图片分类模型。 添加进Arduino库 有两种方法将下载的文件添加进Arduino库。 在Arduino IDE程序中,转到项目选项卡…

NASA数据集——2017年美国阿拉斯加以及加拿大北部二氧化碳探测仪监测的大气后向散射系数剖面图数据集

来自二氧化碳探测仪的大气后向散射系数剖面图,2017年 本数据集提供了2017-07-20至2017-08-08期间在美国阿拉斯加以及加拿大育空地区和西北地区上空进行的二氧化碳夜间、白天和季节排放主动传感(ASCENDS)部署期间收集的大气后向散射系数剖面图…

mysqldump备份数据恢复数据简单使用入门保姆级教程

使用教程 1.基本语法: mysqldump –u user –h host –p 数据库的名称 [ 表名 1 [ 表名 2...]] > 备份文件名称 .sql 2.备份test数据库 特别说明:带--databases或者-B生成的备份语句中有创建数据库语句,否则没有创建数据库语句 mysqldump -uroot -p123456 --…