使用 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()的函数地址.
通过上面两张图表, 我们可以得到如下结论:
- __vfptr是一个指针, 她指向一个函数指针数组(即: 虚函数表)
- 每增加一个虚函数, 只是向该类的虚函数表中增加一项而已, 并不会影响到类对象的大小
既然__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
表明内存空间中仅仅能够包含一份虚基类的子对象,并且通过某种间接的机制来完成共享的引用关系。
网友评论