C++ 类的内存分布

文章目录

  • 1 . 前言
  • 2 . 无继承,无虚函数
  • 3 . 无继承,有虚函数
  • 4 . 单一继承,无虚函数
  • 5 . 单一继承,有虚函数,虚析构
  • 6 . 多重继承
  • 7 . 菱形继承
  • 8 . 虚拟继承
  • 9 . 总结


【极客技术传送门】 : https://blog.csdn.net/Engineer_LU/article/details/135149485


1 . 前言

之前看过一些博主写的类内存排布,这边总结起来描述C++类在继承,虚函数,多继承,虚继承体现的内存排布,本文将直观简洁呈现出来

2 . 无继承,无虚函数

类代码,内存排布,如下所示 :

class Obj_A
{
private:char a;static int b;public:int c;void func() {}
};

在这里插入图片描述
小结 :无继承,无虚函数的情况下
1 . 静态成员编译分布在静态区,因此静态变量b不占用类内存
2 . 成员函数编译分布在代码区,因此成员函数func()不占用类内存
3 . 类的内存排布自顶而下分布,内存对齐按照系统平台编译而定,图中32位系统,因此内存对齐按4字节


3 . 无继承,有虚函数

类代码,内存排布,如下所示 :

class Obj_A
{
private:char a;static int b;public:int c;void func() {}virtual void func1() {}
};

在这里插入图片描述
小结 :无继承,有虚函数的情况下
1 . 只要类中有虚函数,编译就会生成一个 vfptr虚指针 指向虚表vftable , 其中 vfptr虚指针 根据平台所占,32位平台占用四字节


4 . 单一继承,无虚函数

类代码,内存排布,如下所示 :

class Obj_A
{
private:char a;static int b;public:int c;void func() {}
};class Obj_B : public Obj_A
{private:char a;public:int b;};

在这里插入图片描述

小结 :单一继承,无虚函数的情况下
1 . 继承 Obj_A 的内存,并且追加 Obj_B 新增的内存


5 . 单一继承,有虚函数,虚析构

  1. 子类不重写的情况,如下所示 :
class Obj_A
{
private:char a;static int b;public:int c;virtual void func() {}
};class Obj_B : public Obj_A
{
private:char a;public:int b;
};

在这里插入图片描述

  1. 子类重写的情况,如下所示 :
class Obj_A
{
private:char a;static int b;public:int c;virtual void func() {}
};class Obj_B : public Obj_A
{
private:char a;public:int b;virtual void func() {}
};

在这里插入图片描述

小结 :单一继承,有虚函数的情况下,并且讲解为什么加虚析构
1 . 子类对应虚函数重写的情况下,子类的虚表中将覆盖父类虚函数
2 . 在多态中,当通过父类指针删除子类对象时,那么释放时是希望子类父类一起释放的,因此父类的析构设为虚函数,当父类调用时,执行的是子类的析构,这样父类就会跟着一起析构


6 . 多重继承

类代码,内存排布,如下所示 :

class Obj_A
{virtual ~Obj_A();
private:int a;public:virtual void func() {}
};class Obj_B
{virtual ~Obj_B();
private:int a;public:void func() {}
};class Obj_C : public Obj_A, public Obj_B
{virtual ~Obj_C();
private:char a;public:int b;};

在这里插入图片描述

小结 :多继承,有虚函数的情况下
1 . 子类会按顺序继承父类的内存模型
2 . 有虚函数的情况下多重继承,子类会对this指针进行偏移,如图中子类继承父类Obj_A虚表偏移值为0,而子类继承父类Obj_B虚表偏移值为-8,通过this指针偏移,子类Obj_C就可以根据继承的ObjA,ObjB虚表中相对this指针偏移,从而找到继承Obj_B的虚表,以上的-8并不是固定值,取决于Obj_A内存有多大,如Obj_A占内存24,则偏移-24


7 . 菱形继承

类代码,内存排布,如下所示 :

class Obj_A
{virtual ~Obj_A();
private:int a;public:virtual void func() {}
};class Obj_B : public Obj_A
{virtual ~Obj_B();
private:int a;public:virtual void func() {}
};class Obj_C : public Obj_A
{virtual ~Obj_C();
private:int a;public:virtual void func() {}
};class Obj_D : public Obj_B, public Obj_C
{virtual ~Obj_D();
private:int a;public:int b;void func() {}};

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

小结 :菱形继承,有虚函数的情况下
1 . 依然按照常规继承关系执行,只是由于Obj_B与Obj_C都继承了Obj_A,而Obj_D多重继承B与C,因此Obj_D内存模型中可以看到继承了两个Obj_A,内存按顺序排布


8 . 虚拟继承

类代码,内存排布,如下所示 :

class Obj_A
{virtual ~Obj_A();
private:int a;public:virtual void func() {}
};class Obj_B : virtual public Obj_A
{virtual ~Obj_B();
private:int a;public:virtual void func() {}
};class Obj_C : virtual public Obj_A
{virtual ~Obj_C();
private:int a;public:virtual void func() {}
};class Obj_D : public Obj_B, public Obj_C
{virtual ~Obj_D();
private:int a;public:int b;void func() {}};

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

小结 :虚拟继承情况下
1 . 将子类的内存放内存最前面,后面再放父类的内存,与常规内存继承反过来
2 . 产生两个虚指针,分别指向自身虚表与父类虚表
3 . 由于虚继承的父类内存模型放最后,所以前面有虚继承时,当后面有多重继承,会先把多继承的内存模型放前面,最后才放虚继承的父类,这样的好处就是不需要重复继承父类


9 . 总结

上面最后有几种情况没提到区分虚函数情况,是因为前面已经总结出有无虚函数主要体现在虚指针与虚表,所以后面直接以带虚函数来总结,全文总结了以下七种内存模型情况 :

无继承,无虚函数 的情况下 :
1 . 静态成员编译分布在静态区,因此静态变量b不占用类内存
2 . 成员函数编译分布在代码区,因此成员函数func()不占用类内存
3 . 类的内存排布自顶而下分布,内存对齐按照系统平台编译而定,图中32位系统,因此内存对齐按4字节

无继承,有虚函数 的情况下
1 . 只要类中有虚函数,编译就会生成一个 vfptr虚指针 指向虚表vftable , 其中 vfptr虚指针 根据平台所占,32位平台占用四字节

单一继承,无虚函数 的情况下
1 . 继承 Obj_A 的内存,并且追加 Obj_B 新增的内存

单一继承,有虚函数 的情况下,并且讲解为什么加虚析构
1 . 子类对应虚函数重写的情况下,子类的虚表中将覆盖父类虚函数
2 . 在多态中,当通过父类指针删除子类对象时,那么释放时是希望子类父类一起释放的,因此父类的析构设为虚函数,当父类调用时,执行的是子类的析构,这样父类就会跟着一起析构

多继承 的情况下
1 . 子类会按顺序继承父类的内存模型
2 . 有虚函数的情况下多重继承,子类会对this指针进行偏移,如图中子类继承父类Obj_A虚表偏移值为0,而子类继承父类Obj_B虚表偏移值为-8,通过this指针偏移,子类Obj_C就可以根据继承的ObjA,ObjB虚表中相对this指针偏移,从而找到继承Obj_B的虚表,以上的-8并不是固定值,取决于Obj_A内存有多大,如Obj_A占内存24,则偏移-24

菱形继承 的情况下
1 . 依然按照常规继承关系执行,只是由于Obj_B与Obj_C都继承了Obj_A,而Obj_D多重继承B与C,因此Obj_D内存模型中可以看到继承了两个Obj_A,内存按顺序排布

虚拟继承 情况下
1 . 将子类的内存放内存最前面,后面再放父类的内存,与常规内存继承反过来
2 . 产生两个虚指针,分别指向自身虚表与父类虚表
3 . 由于虚继承的父类内存模型放最后,所以前面有虚继承时,当后面有多重继承,会先把多继承的内存模型放前面,最后才放虚继承的父类,这样的好处就是不需要重复继承父类


技术交流QQ群 : 745662457

  • 问题答疑,技术交流

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

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

相关文章

淘宝京东1688商品详情API接口,搜索商品列表接口

前言 在实际工作中,我们需要经常跟第三方平台打交道,可能会对接第三方平台API接口,或者提供API接口给第三方平台调用。 那么问题来了,如果设计一个优雅的API接口,能够满足:安全性、可重复调用、稳定性、好…

vue-video-player播放hls视频流

需求 最近需要接入海康视频摄像头,然后把视频的画面接入到自己的网站系统中。以前对接过rtsp固定IP的显示视频,这次的不一样,没有了固定IP。海康的解决办法是,摄像头通过配置服务器到萤石云平台,然后购买企业版账号和…

Window端口占用处理

您好,我是码农飞哥(wei158556),感谢您阅读本文,欢迎一键三连哦。 💪🏻 1. Python基础专栏,基础知识一网打尽,9.9元买不了吃亏,买不了上当。 Python从入门到精…

智能语音机器人NXCallbot

受出海公司业务全球化的影响,智能客服逐渐从便捷应用变为市场刚需。新基建七大领域中,人工智能及场景应用的基础建设是最核心的领域,而智能客服作为商业化实际应用的核心场景之一,能提升企业运营效率,为行业客户赋能。…

目标检测-One Stage-RetinaNet

文章目录 前言一、RetinaNet的网络结构和流程二、RetinaNet的创新点Balanced Cross EntropyFocal Loss 总结 前言 根据前文目标检测-One Stage-YOLOv2可以看出YOLOv2的速度和精度都有相当程度的提升,但是One Stage目标检测模型仍存在一个很大的问题: 前…

ARM NEON 指令

NEON指令 按照操作数类型可以分为正常指令、宽指令、窄指令、饱和指令、长指令。 正常指令:生成大小相同且类型通常与操作数向量相同到结果向量。长指令:对双字向量操作数执行运算,生产四字向量到结果。所生成的元素一般是操作数元素宽度到…

ReentrantLock底层原理学习一

J.U.C 简介 Java.util.concurrent 是在并发编程中比较常用的工具类,里面包含很多用来在并发场景中使用的组件。比如线程池、阻塞队列、计时器、同步器、并发集合等等。并发包的作者是大名鼎鼎的 Doug Lea。我们在接下来的课程中,回去剖析一些经典的比较…

vite4项目中,vant兼容750适配

一般非vite项目,使用postcss-px-to-viewport。在设计稿为750时候,可使用以下配置兼容vant。 在vite4项目中,以上配置不行。需要调整下,使用postcss-px-to-viewport-8-plugin,并修改viewportWidth,具体如下…

CSS新增文本样式(完整)

文本样式 概念:在CSS3中,增加了丰富的文本修饰效果,使得页面更加美观舒服。 常用的文本样式属性 属性说明text-shadow文本阴影text-stroke文本描边text-align-last文本对齐white-space处理空白字符text-overflow文本溢出word-wrap | word-…

【Linux 内核源码分析】关于Linux内核源码目录结构

Linux内核源码采用树形结构。功能相关的文件放到不同的子目录下面,使程序更具有可读行。 使用Source Insight打开源码,如下图所示,可以看到源码是树形结构。 目录含义描述arch存放与体系结构相关的代码,包括不同硬件平台的特定代…

Qt/QML编程学习之心得:QSocketNotifier(二十一)

QSocketNotifier在Qt中怎么使用? QSocketNotifier使Qt的事件循环与其他基于文件描述符的事件循环集成成为可能。在Qt的主事件循环(QCoreApplication::exec())中检测到文件描述符操作。 使用低级(通常是特定于平台的)API打开设备后,可以创建一个套接字通知程序来监视文…

DDoS攻击的多种方式

DDOS攻击指分布式拒绝服务攻击,即处于不同位置的多个攻击者同时向一个或数个目标发动攻击,或者一个攻击者控制了位于不同位置的多台机器并利用这些机器对受害者同时实施攻击。由于攻击的发出点是分布在不同地方的,这类攻击称为分布式拒绝服务…

leetcode:2451. 差值数组不同的字符串(python3解法)

难度&#xff1a;简单 给你一个字符串数组 words &#xff0c;每一个字符串长度都相同&#xff0c;令所有字符串的长度都为 n 。 每个字符串 words[i] 可以被转化为一个长度为 n - 1 的 差值整数数组 difference[i] &#xff0c;其中对于 0 < j < n - 2 有 difference[i]…

【unity小技巧】FPS游戏实现相机的偏移震动、武器射击后退和后坐力效果

最终效果 文章目录 最终效果前言相机偏移震动相机震动脚本换弹节点震动 武器射击后退效果武器后坐力效果完结 前言 关于后坐力之前其实已经分享了一个&#xff1a;FPS游戏后坐力制作思路 但是实现起来比较复杂&#xff0c;如果你只是想要简单的实现&#xff0c;可以看看这个&…

使用printJS使网页打印成PDF、网页html结合printJS导出为pdf

先放几个参考链接 感谢&#xff01; Vue使用PrintJS实现页面打印功能_vue print.js 设置打印pdf的大小-CSDN博客 前台导出pdf经验汇总 &#xff08;html2canvas.js和浏览器自带的打印功能-print.js&#xff09;以及后台一些导出pdf的方法_iqc后台管理系统怎么做到导出pdf-CSD…

大创项目推荐 深度学习图像分类算法研究与实现 - 卷积神经网络图像分类

文章目录 0 前言1 常用的分类网络介绍1.1 CNN1.2 VGG1.3 GoogleNet 2 图像分类部分代码实现2.1 环境依赖2.2 需要导入的包2.3 参数设置(路径&#xff0c;图像尺寸&#xff0c;数据集分割比例)2.4 从preprocessedFolder读取图片并返回numpy格式(便于在神经网络中训练)2.5 数据预…

ssm基于JSP的网络游戏交易系统的设计与实现+jsp论文

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;商品管理信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不能满足…

面试题理解深层次的数组名

目录 引言 一&#xff1a;一维数组 举例如下 1.铺垫知识 数组名是数组首元素的地址&#xff0c;但是有两个特殊情况 &#xff08;1&#xff09;sizeof(数组名) &#xff08;2&#xff09;&数组名 2.分析讲解上述代码结果 2.字符数组 举例一如下 1.知识铺垫 …

(三)其他的输入输出

文章目录 getchar();单个字符输入使用&#xff1a; putchar();单个字符输出(自带换行)使用 puts();字符串输出与printf区别使用 gets();后面补充 代码现象 getchar(); 单个字符输入 使用&#xff1a; 变量 getchar(); 例&#xff1a;char a&#xff1b; a getchar(); put…

软件测试之单元测试、系统测试、集成测试知识总结

一、单元测试的概念 单元测试是对软件基本组成单元进行的测试&#xff0c;如函数或一个类的方法。当然这里的基本单元不仅仅指的是一个函数或者方法&#xff0c;有可能对应多个程序文件中的一组函数。 单元也具有一些基本的属性。比如&#xff1a;明确的功能、规格定义&#…