C++ 类的内存分布

2024-01-07 18:52:45


【极客技术传送门】 : 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

  • 问题答疑,技术交流

文章来源:https://blog.csdn.net/Engineer_LU/article/details/135426516
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。