美文网首页C和C++常用
C++类的内存分布

C++类的内存分布

作者: 头发茂密的程序员 | 来源:发表于2019-07-15 15:39 被阅读0次

    使用 Microsoft Visual Studio 查看类的内存分布方式:
    项目—>属性—>C/C++—>命令行—> /d1 reportAllClassLayout (查看单所有类的内存)
    项目—>属性—>C/C++—>命令行—> /d1 reportSingleClassLayoutxxx (查看单个类的内存, xxx为要查看内存分布的类名)
    查看所有类可能会不好找自己定义的类(会将头文件中的类全部显示出来), 建议使用查看单个类的方式(当然可能是我不会用,会的大佬欢迎留言告知)

    1.类的内存分布

    定义一个动物类(Animal), 有两个成员变量: 1.重量(weight), 2.脚的数量(legs), 以及一个成员函数(CommFunc()), 则内存显示如下:
    (为了节省篇幅, 后面的代码只贴出类的定义)

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class Animal {
    public:
        void CommFunc() {};
    
    protected:
        int weight; //! 重量
        int legs;   //! 脚的数量
    };
    
    int main()
    {
        return 0;
    }
    ================================================
    1>  test.cpp
    1>
    1>  class Animal    size(8):
    1>      +---
    1>   0  | weight
    1>   4  | legs
    1>      +---
    

    为了不考虑内存对齐和其他的情况,变量全部用int表示,可以看出Animal总size为8个字符长度,成员变量依次排列(变量名前面的数字0和4表示内存偏移), 成员函数不占内存空间.

    2.继承类的内存分布

    定义一个Bird的子类继承Animal, 有自己的成员变量(wing)和成员函数(Fly())

    class Animal {
    public:
        void CommFunc() {};
    
    protected:
        int weight; //! 重量
        int legs;   //! 脚的数量
    };
    
    class Bird:public Animal
    {
    public:
        void Fly();
    private:
        int wing;   //! 翅膀
    };
    ================================================
    1>  test.cpp
    1>
    1>  class Bird  size(12):
    1>      +---
    1>   0  | +--- (base class Animal)
    1>   0  | | weight
    1>   4  | | legs
    1>      | +---
    1>   8  | wing
    1>      +---
    

    可以看到在内存分布上,先是子类继承了的父类的成员变量, 然后是自己的成员变量,成员函数依旧不占内存

    3.仅一个虚函数的类的内存分布
    class Animal {
    public:
        void CommFunc() {};
        virtual void VirtualFunc() {};
    
    protected:
        int weight; //! 重量
        int legs;   //! 脚的数量
    };
    ================================================
    1>  test.cpp
    1>
    1>  class Animal    size(12):
    1>      +---
    1>   0  | {vfptr}
    1>   4  | weight
    1>   8  | legs
    1>      +---
    1>
    1>  Animal::$vftable@:
    1>      | &Animal_meta
    1>      |  0
    1>   0  | &Animal::VirtualFunc
    1>
    1>  Animal::VirtualFunc this adjustor: 0
    

    这时候我们看到内存分布分为两部分,上面和之前显示的一样为内存分布, 下面多了一个虚表. 先看上面, 发现和之前相比多了一个{vfptr}(占4个字节)也就是常说的虚函数表vtable指针.
    下面则生成了虚表,紧跟在&Animal_meta后面的0表示,这张虚表对应的虚指针在内存中的分布(即上面{vfptr}变量的偏移),下面列出了虚函数, 左侧的0是虚函数的序号,右边是虚函数的函数名

    虚函数内存分布

    通过调试可以看到虚表指针{vfptr}的类型为 void**
    其[0]元素, 其类型为void*,值为 Animal::VirtualFunc() 函数的地址.

    4.有多个虚函数的类的内存分布
    class Animal {
    public:
        void CommFunc();
        virtual void VirtualFunc_1() {};
        virtual void VirtualFunc_2() {};
    
    protected:
        int weight; //! 重量
        int legs;   //! 脚的数量
    };
    ================================================
    1>  test.cpp
    1>
    1>  class Animal    size(12):
    1>      +---
    1>   0  | {vfptr}
    1>   4  | weight
    1>   8  | legs
    1>      +---
    1>
    1>  Animal::$vftable@:
    1>      | &Animal_meta
    1>      |  0
    1>   0  | &Animal::VirtualFunc_1
    1>   1  | &Animal::VirtualFunc_2
    1>
    1>  Animal::VirtualFunc_1 this adjustor: 0
    1>  Animal::VirtualFunc_2 this adjustor: 0
    

    我们看到Animal类的size依旧是12个字节, 但是虚表中多了一个函数


    image.png

    通过调试可以看出虚表指针指向的地址中多了一个[1], 其值为Animal类的第二个虚函数VirtualFunc_2()的函数地址.

    通过上面两张图表, 我们可以得到如下结论:

    1. __vfptr是一个指针, 她指向一个函数指针数组(即: 虚函数表)
    2. 每增加一个虚函数, 只是向该类的虚函数表中增加一项而已, 并不会影响到类对象的大小

    既然__vfptr是一个指针, 指向虚函数表, 那么虚函数表和对象是怎么关联的呢?我们定义两个变量,看看__vfptr指针的指向


    image.png

    通过调试图可以发现变量Cat和Dog指向同一个虚函数表
    于是可以得出

    同一个类的不同实例共用一份虚函数表

    5.继承且继承类不存在虚函数的内存分布
    class Animal {
    public:
        void CommFunc();
        virtual void VirtualFunc_1() {};
        virtual void VirtualFunc_2() {};
    
    protected:
        int weight; //! 重量
        int legs;   //! 脚的数量
    };
    
    class Bird:public Animal
    {
    public:
        void Fly();
    private:
        int wing;   //! 翅膀
    };
    ================================================
    1>  test.cpp
    1>
    1>  class Bird  size(16):
    1>      +---
    1>   0  | +--- (base class Animal)
    1>   0  | | {vfptr}
    1>   4  | | weight
    1>   8  | | legs
    1>      | +---
    1>  12  | wing
    1>      +---
    1>
    1>  Bird::$vftable@:
    1>      | &Bird_meta
    1>      |  0
    1>   0  | &Animal::VirtualFunc_1
    1>   1  | &Animal::VirtualFunc_2
    

    前面依旧是基类的内存分布(虚函数表指针+成员变量), 然后接着是子类的成员变量

    6.继承类存在虚函数覆盖的内存分布
    class Animal {
    public:
        void CommFunc();
        virtual void VirtualFunc_1() {};
        virtual void VirtualFunc_2() {};
    
    protected:
        int weight; //! 重量
        int legs;   //! 脚的数量
    };
    
    class Bird:public Animal
    {
    public:
        void Fly() {};
        void VirtualFunc_1() {};
    private:
        int wing;   //! 翅膀
    };
    ================================================
    1>  test.cpp
    1>
    1>  class Bird  size(16):
    1>      +---
    1>   0  | +--- (base class Animal)
    1>   0  | | {vfptr}
    1>   4  | | weight
    1>   8  | | legs
    1>      | +---
    1>  12  | wing
    1>      +---
    1>
    1>  Bird::$vftable@:
    1>      | &Bird_meta
    1>      |  0
    1>   0  | &Bird::VirtualFunc_1
    1>   1  | &Animal::VirtualFunc_2
    1>
    1>  Bird::VirtualFunc_1 this adjustor: 0
    

    上半部分内存的分布没有变化. 虚函数表的0号变成了Bird::VirtualFunc_1
    同样我们实例化一个基类变量和一个子类变量,看看他们的虚函数表的区别.


    image.png

    利用vs调试可以看到:
    1.基类的实例化对象dog的虚函数表指针指向的地址是(0x00e36b34);
    2.子类的实例化对象b1的虚函数表指针指向的地址是(0x00e36be8);
    说明两个对象的虚函数表各自独立.
    再看看虚函数表里面的内容,
    3.dog的[0]虚函数指针指向的是基类的Animal::VirtualFunc_1()函数(0x003c1375);
    4.b1的[0]虚函数指针指向的是子类的Bird::VirtualFunc_1()函数(0x003c137f);
    5.dog和b1的[1]虚函数指针指向的都是基类的Animal::VirtualFunc_2()函数(0x003c1370)
    可以看出

    子类有自己独立的虚函数表,虚函数表的内容从基类拷贝,并覆盖重写的虚函数.

    7.子类定义了基类没有的虚函数的继承的类的内存分布
    class Animal {
    public:
        void CommFunc();
        virtual void VirtualFunc_1() {};
        virtual void VirtualFunc_2() {};
    
    protected:
        int weight; //! 重量
        int legs;   //! 脚的数量
    };
    
    class Bird:public Animal
    {
    public:
        void Fly() {};
        void VirtualFunc_1() {};
        virtual void Bird_VirtualFunc() {};
    private:
        int wing;   //! 翅膀
    };
    ================================================
    1>  test.cpp
    1>
    1>  class Bird  size(16):
    1>      +---
    1>   0  | +--- (base class Animal)
    1>   0  | | {vfptr}
    1>   4  | | weight
    1>   8  | | legs
    1>      | +---
    1>  12  | wing
    1>      +---
    1>
    1>  Bird::$vftable@:
    1>      | &Bird_meta
    1>      |  0
    1>   0  | &Bird::VirtualFunc_1
    1>   1  | &Animal::VirtualFunc_2
    1>   2  | &Bird::Bird_VirtualFunc
    1>
    1>  Bird::VirtualFunc_1 this adjustor: 0
    1>  Bird::Bird_VirtualFunc this adjustor: 0
    

    上面的部分没有变化,下面虚函数表的内容变化了.虚函数表多了一个2号内容为子类独有的虚函数Bird_VirtualFunc(),即子类的虚函数排在基类虚函数后面.

    8.多继承的类的内存分布
    class Animal {
    public:
        void CommFunc() {};
        virtual void VirtualFunc_1() {};
        virtual void VirtualFunc_2() {};
    
    protected:
        int weight; //! 重量
        int legs;   //! 脚的数量
    };
    
    class Animal2 {
    public:
        void CommFunc2() {};
        virtual void VirtualFunc2_1() {};
        virtual void VirtualFunc2_2() {};
    
    protected:
        int hands;  //! 手的数量
    };
    
    class Dragon :public Animal, public Animal2
    {
    public:
        void Fly() {};
        void VirtualFunc_1() {};    //! 重写 Animal 类的 VirtualFunc_1()
        void VirtualFunc2_2() {};   //! 重写 Animal2 类的 VirtualFunc2_2()
        virtual void Dragon_VirtualFunc() {};   //! 独有的虚函数
    private:
        int wing;   //! 翅膀
    };
    ================================================
    1>  test.cpp
    1>
    1>  class Dragon    size(24):
    1>      +---
    1>   0  | +--- (base class Animal)
    1>   0  | | {vfptr}
    1>   4  | | weight
    1>   8  | | legs
    1>      | +---
    1>  12  | +--- (base class Animal2)
    1>  12  | | {vfptr}
    1>  16  | | hands
    1>      | +---
    1>  20  | wing
    1>      +---
    1>
    1>  Dragon::$vftable@Animal@:
    1>      | &Dragon_meta
    1>      |  0
    1>   0  | &Dragon::VirtualFunc_1
    1>   1  | &Animal::VirtualFunc_2
    1>   2  | &Dragon::Dragon_VirtualFunc
    1>
    1>  Dragon::$vftable@Animal2@:
    1>      | -12
    1>   0  | &Animal2::VirtualFunc2_1
    1>   1  | &Dragon::VirtualFunc2_2
    1>
    1>  Dragon::VirtualFunc_1 this adjustor: 0
    1>  Dragon::VirtualFunc2_2 this adjustor: 12
    1>  Dragon::Dragon_VirtualFunc this adjustor: 0
    

    内存分布依次继承Animal和Animal2(包括了两个基类各自的虚函数表).
    下面的虚函数表内容有两个(对应两个基类的虚函数表),可以看到Dragon独有的虚函数保存在第一个(即Animal)虚函数表中,而下面那个虚函数表有一个(-12),在上面有说到,这个数字标识这张虚表对应的虚指针在内存中的分布(即面{vfptr}变量的偏移),可以看出偏移的长度刚好是第一个基类(Animal)所占内存大小.
    既然Dragon独有的虚函数保存在第一个(即Animal)虚函数表中,那么如果第一个基类没有虚函数,子类的虚函数应该怎么存储呢?

    9.第一个基类没有虚函数的多继承的类的内存分布
    class Animal {
    public:
        void CommFunc() {};
    
    protected:
        int weight; //! 重量
        int legs;   //! 脚的数量
    };
    
    class Animal2 {
    public:
        void CommFunc2() {};
        virtual void VirtualFunc2_1() {};
        virtual void VirtualFunc2_2() {};
    
    protected:
        int hands;  //! 手的数量
    };
    
    class Dragon :public Animal, public Animal2
    {
    public:
        void Fly() {};
        void VirtualFunc2_2() {};   //! 重写 Animal2 类的 VirtualFunc2_2()
        virtual void Dragon_VirtualFunc() {};   //! 独有的虚函数
    private:
        int wing;   //! 翅膀
    };
    ================================================
    1>  test.cpp
    1>
    1>  class Dragon    size(20):
    1>      +---
    1>   0  | +--- (base class Animal2)
    1>   0  | | {vfptr}
    1>   4  | | hands
    1>      | +---
    1>   8  | +--- (base class Animal)
    1>   8  | | weight
    1>  12  | | legs
    1>      | +---
    1>  16  | wing
    1>      +---
    1>
    1>  Dragon::$vftable@:
    1>      | &Dragon_meta
    1>      |  0
    1>   0  | &Animal2::VirtualFunc2_1
    1>   1  | &Dragon::VirtualFunc2_2
    1>   2  | &Dragon::Dragon_VirtualFunc
    1>
    1>  Dragon::VirtualFunc2_2 this adjustor: 0
    1>  Dragon::Dragon_VirtualFunc this adjustor: 0
    

    内存分布依次继承Animal和Animal2(包括了Animal2的虚函数表).
    下面的虚函数表内容只有一个,可以看到Dragon独有的虚函数保存在基类(即Animal2)虚函数后面.
    可以得出

    子类的虚函数存在第一个有虚函数的基类的虚函数表中

    9.虚继承

    虚继承:类D继承自类B1、B2,而类B1、B2都继承自类A

    class Animal {
    public:
        void CommFunc() {};
        virtual void VirtualFunc_1() {};
        virtual void VirtualFunc_2() {};
    
    protected:
        int weight; //! 重量
        int legs;   //! 脚的数量
    };
    
    class FlyAnimal :public Animal
    {
    public:
        virtual void Fly() {};
    private:
        int wing;   //! 翅膀
    };
    
    class SwimAnimal :public Animal
    {
    public:
        virtual void Swin() {};
    private:
        int height; //! 高度
    };
    
    class Goose :public FlyAnimal, public SwimAnimal
    {
    public:
        void VirtualFunc_1() {};    //! 重写基类 Animal 的 VirtualFunc_1()
        void VirtualFunc_2() {};    //! 重写基类 Animal 的 VirtualFunc_1()
        void Fly() {};              //! 重写基类 FlyAnimal 的 VirtualFunc_1()
        void Swin() {};             //! 重写基类 SwimAnimal 的 VirtualFunc_1()
    
        virtual void Eat() {};      //! 独有的虚函数 Eat()
    private:
        int collor; //! 颜色
    };
    ================================================
    1>  test.cpp
    1>
    1>  class Goose size(36):
    1>      +---
    1>   0  | +--- (base class FlyAnimal)
    1>   0  | | +--- (base class Animal)
    1>   0  | | | {vfptr}
    1>   4  | | | weight
    1>   8  | | | legs
    1>      | | +---
    1>  12  | | wing
    1>      | +---
    1>  16  | +--- (base class SwimAnimal)
    1>  16  | | +--- (base class Animal)
    1>  16  | | | {vfptr}
    1>  20  | | | weight
    1>  24  | | | legs
    1>      | | +---
    1>  28  | | height
    1>      | +---
    1>  32  | collor
    1>      +---
    1>
    1>  Goose::$vftable@FlyAnimal@:
    1>      | &Goose_meta
    1>      |  0
    1>   0  | &Goose::VirtualFunc_1
    1>   1  | &Goose::VirtualFunc_2
    1>   2  | &Goose::Fly
    1>   3  | &Goose::Eat
    1>
    1>  Goose::$vftable@SwimAnimal@:
    1>      | -16
    1>   0  | &thunk: this-=16; goto Goose::VirtualFunc_1
    1>   1  | &thunk: this-=16; goto Goose::VirtualFunc_2
    1>   2  | &Goose::Swin
    1>
    1>  Goose::VirtualFunc_1 this adjustor: 0
    1>  Goose::VirtualFunc_2 this adjustor: 0
    1>  Goose::Fly this adjustor: 0
    1>  Goose::Swin this adjustor: 16
    1>  Goose::Eat this adjustor: 0
    

    SwimAnimal的虚函数表的虚基类指针是
    this-=16; goto Goose::VirtualFunc_1和
    this-=16; goto Goose::VirtualFunc_2
    表明内存空间中仅仅能够包含一份虚基类的子对象,并且通过某种间接的机制来完成共享的引用关系。

    相关文章

      网友评论

        本文标题:C++类的内存分布

        本文链接:https://www.haomeiwen.com/subject/lxvgkctx.html