美文网首页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++

    排序算法总结 对十二种排序算法进行总结C++ 类内存分布 这里不妨说下 C++ 内存分布结构,我们来看看编译器是怎...

  • C++类内存分布

    书上类继承相关章节到这里就结束了,这里不妨说下C++内存分布结构,我们来看看编译器是怎么处理类成员内存分布的,特别...

  • C++类的内存分布

    使用 Microsoft Visual Studio 查看类的内存分布方式:项目—>属性—>C/C++—>命令行—...

  • 程序在内存中的分布

    C语言程序在内存中的分布 代码示例: C++程序在内存中的分布: 代码示例: 以上内容参考以下几篇文章: http...

  • iOS底层原理:类&类的结构分析

    在分析类的结构之前,我们需要先搞清楚类的内存分布情况。 类的内存分布 首先创建一个Person类: 通过上图中可以...

  • C++内存分布

    前言 之前阿里面试的时候有个面试官就问了我会不会"什么什么的内存模型",当时自己还不知道这个名词(知道概念,但确确...

  • C++内存分布

    下面这张图很详细的描述了C++中各种内存区域。 一般分为以下几个区域: 代码区 存放CPU执行的机器指令,代码区是...

  • C/C++内存相关

    1. C/C++语言内存分布 一个C/C++编译的程序所占用的系统内存一般分为以下几个部分的内容: 1) 字符起始...

  • Objective-C对象内存分布是怎样确定的

    对于一个类的实例变量来说,我们常说他的内存分布是isa + ivars。为什么内存是这样分布的?他是怎样确定的? ...

  • c++内存管理

    c++内存管理长文 c++内存管理

网友评论

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

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