C++类的内存分布

使用 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
表明内存空间中仅仅能够包含一份虚基类的子对象,并且通过某种间接的机制来完成共享的引用关系。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容

  • 书上类继承相关章节到这里就结束了,这里不妨说下C++内存分布结构,我们来看看编译器是怎么处理类成员内存分布的,特别...
    saviochen阅读 682评论 0 7
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,096评论 1 32
  • 几种语言的特性 汇编程序:将汇编语言源程序翻译成目标程序编译程序:将高级语言源程序翻译成目标程序解释程序:将高级语...
    囊萤映雪的萤阅读 2,885评论 1 5
  • 1、针对字符串的操作 正则表达式是针对字符串的操作,所以对于字符串来说,即使不用正则表达工式也可以实现相同的效果,...
    李悦之阅读 495评论 0 0
  • 两个朋友的真实人生故事: 1.小明努力读书最终考上不错的学校,毕业后,顺利找到了一份大企业的优渥工作,然后努力工作...
    Link_be41阅读 231评论 0 0