C++ 类的内存分布

news2024/9/29 17:23:43

文章目录

  • 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.coloradmin.cn/o/1363377.html

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

相关文章

CentOS:docker容器日志清理

1.先查看磁盘空间 df -h 2.找到容器的containerId-json.log文件,并清理 find /var/lib/docker/containers/ -name *-json.log |xargs du -sh 3、可以根据需求清理对应日志也可以清理数据大的日志 $ cat /dev/null > /var/lib/docker/containers/dbaee0746cc6adad3768b4ef…

Java快速排序希尔排序归并排序

快速排序算法 快速排序的原理:选择一个关键值作为基准值。比基准值小的都在左边序列(一般是无序的),比基准值大的都在右边(一般是无序的)。一般选择序列的第一个元素。 一次循环:从后往前比较&…

软件测试|什么是Python构造方法,构造方法如何使用?

构造方法(Constructor)是面向对象编程中的重要概念,它在创建对象时用于初始化对象的实例变量。在Python中,构造方法是通过特殊的名称__init__()来定义的。本文将介绍Python构造方法的基本概念、语法和用法。 什么是构造方法&…

Qt添加资源文件

ui->setupUi(this);//1. 使用本地文件:ui->actionasdasdas->setIcon(QIcon("本地绝对路径"));ui->actiona1->setIcon(QIcon("C:/Users/满满/Desktop/output/picture/1.jpg"));//2. 使用资源文件:ui->actionasdasd…

二叉树基础oj练习(单值二叉树、相同的树、二叉树的前序遍历)

讲了这么多数据结构相关的知识(可以看我的数据结构文章专栏): 抓紧刷题巩固一下了 目录 1.单值二叉树 题目描述 思路1 代码1 思路2 代码2 2.相同的树 题目描述 思路 代码 3.二叉树的前序遍历 代码 思路 1.单值二叉树 965. 单值二叉树 - 力扣(LeetCod…

NVM NodeJs版本管理 通关宝典

NVM NodeJs版本管理 通关宝典🏹 文章目录 NVM NodeJs版本管理 通关宝典🏹一、NVM是什么二、开始使用NVM三、NVM 命令速查四、手动安装特定Node版本(Windows)🙄4.1 NVM for windows 运行机制4.2 手动安装流程 五、切换 NVM 下载镜像源六、常见…

docker拉取镜像提示 remote trust data does not exist for xxxxxx

1、How can I be sure that I am pulling a trusted image from docker 2、docker: you are not authorized to perform this operation: server returned 401. 以上两个问题可以试试以下解决办法 DOCKER_CONTENT_TRUSTfalse 本人是使用jenkins部署自己的项目到docker容器出现…

【技巧】IDEA 使用小技巧(三)

IDEA 使用小技巧(三) 配置目录Ctrl 鼠标方法缩小字体 配置目录 IDEA 在使用的过程中会在 C 盘的用户目录下写入相关配置,目录如下: "C:\Users\个人用户名\AppData\Local\JetBrains" "C:\Users\个人用户名\AppDa…

在线制作假期承诺书,电子手写签名确认,一键导出打印。

假期将至,为积极落实安全管理规定,单位通常需要下发安全承诺书进行签字确认。 易查分可以实现网上下发安全承诺书通知,让查询者进行签名确认,还可以生成PDF,方便打印一人一张的纸质版承诺书,本次就来介绍如…

C#编程-实现函数重载

考虑一个示例:您必须编写一个程序来实现计算器的功能。计算器执行各种运算,例如数字的加、减及乘等。可以对任何类型的数据执行这些运算。这是否意味着您必须定义单独的函数名(如addInteger、addFloat和addDoublie)对每种此类数字…

扫码支付是怎么工作的?

扫码支付是怎么工作的? 本文转自 公众号 ByteByteGo,如有侵权,请联系,立即删除 过去的几十年,支付技术发生了很大的改变。下图给我们显示了 POS 终端的进化。从一开始的纸钞收银机,到刷卡机,再到…

Python 教程 02:Python 编程环境的搭建与 IDE 的选择

目录 一、搭建 Python 环境 1.1 Python 官网 1.2 下载 Python 1.2.1 选择版本 1.2.2 选择平台 1.2.3 下载安装文件(Windows & macOS) 1.3 安装环境 1.3.1 Windows 平台 1.3.2 macOS 平台 1.3.3 Linux 平台 1.4 验证安装是否成功 二、选择…

OpenHarmony应用构建工具Hvigor的构建流程

前言 OpenHarmony 应用和服务使用 Hvigor 作为工程的构建工具。本篇文章将介绍 Hvigor 的构建流程,通过修改脚本配置使 Hvigor 执行自定义任务。 Hvigor 的构建流程 加载命令行参数和环境变量;初始化项目结构,创建 Project 和 Module 实例…

计算机网络-VLAN原理与配置

之前我们学习了以太网的基础知识,了解了网络交换设备的发展,交换机的工作原理,广播域和冲突域。 一、概述 还简单了解了以太网的CSMA/CD通讯机制,以太网是建立在CSMA/CD (Carrier Sense Multiple Access/Collision Detection&…

C++ 学习笔记之运算符重载+案例

目录 一、C 运算符重载 二、定义一个成员函数或全局函数 三、计算时间 1.计算时间差 2.时间加减 四、一个运算符重载实例 一、C 运算符重载 是一种特性,它允许程序员重新定义已有的运算符的行为,以适应自定义类型的操作。通过运算符重载&#xff0…

java内存屏障

参考:https://blog.csdn.net/weixin_73077810/article/details/132804522 内存屏障主要用于解决线程的可见性、有序性问题 代码案例: ReentrantLock保证可见性的原理 在 lock.lock() 和 lock.unlock() 时,都会操作 AbstractQueuedSy…

UEditor在编辑对齐方式时产生额外空行问题

一、问题描述 一个关于UEditor富文本编辑器的问题:在编辑内容对齐方式后保存后浏览器显示的段落上下会比原先多出一些间距。 下面是对齐编辑后,未保存前的的HTML: 保存后,实际会多出一个段落空行: 二、问题调查 经…

Java多线程技术11——ThreadPoolExecutor类的使用1-备份

1 概述 ThreadPoolExecutor类可以非常方便的创建线程池对象,而不需要程序员设计大量的new实例化Thread相关的代码。 2 队列LinkedBlockingQueue的使用 public class Test1 {public static void main(String[] args) {LinkedBlockingQueue queue new LinkedBlocki…

字节跳动基础架构SRE-Copilot获得2023 CCF国际AIOps挑战赛冠军

近日,2023 CCF国际AIOps挑战赛决赛暨“大模型时代的AIOps”研讨会在北京成功举办,活动吸引了来自互联网、运营商、科研院所、高校、软硬件厂商等领域多名专家学者参与,为智能运维的前沿学术研究、落地生产实践打开了新思路。决赛中&#xff0…

leetcode算法题之记忆化搜索总结

记忆化搜索,可以理解为带备忘录的递归,方便进行剪枝,是一种以空间换时间的策略。 本章目录 1.斐波那契数2.不同路径3.最长递增子序列4.猜数字大小II5.矩阵中的最长递增路径 1.斐波那契数 斐波那契数 class Solution { public://递归int f…