在windows下vs中使用的是MSVC的 未公开开关d1reportAllClassLayout
我是在学习类内存分布时找到了这个问题和办法。
C++ 类 内存分布 虚函数 单继承 多继承
重点内容
一、首先看看没有继承情况下类的内存分布:
1.1 定义一个简单的类,没有虚函数。
代码如下:
#include<iostream>
using namespace std;
class Base
{
public:
int Base_1;
int Base_2;
public:
void func1();
};
int main() {
Base obj_base;
return 0;
}
vs编译设置:
其中,下面命令中的Base对应要查看的类名Base:
/d1 reportSingleClassLayoutBase
编译完成后,在output窗口有如下内容,可以用于分析内存分布:
1> class Base size(8):
1> +---
1> 0 | Base_1
1> 4 | Base_2
1> +---
从这里可以看到普通类的内存排布方式,成员变量依据声明的顺序进行排列,类内偏移从0开始,普通成员函数不占内存空间。这里没有考虑char等成员变量的字节对齐方式等问题。
1.2 定义一个简单的类,包含虚函数。
#include<iostream>
using namespace std;
class Base
{
public:
int Base_1;
int Base_2;
public:
void func1();
virtual void func2(){};
virtual void func3(){};
};
int main(){
Base obj_base;
return 0;
}
编译,得到的内存分布图如下所示:
1> class Base size(12):
1> +---
1> 0 | {vfptr}
1> 4 | Base_1
1> 8 | Base_2
1> +---
1>
1> Base::$vftable@:
1> | &Base_meta
1> | 0
1> 0 | &Base::func2
1> 1 | &Base::func3
1>
1> Base::func2 this adjustor: 0
1> Base::func3 this adjustor: 0
从图中可以看出,这个内存结构图分成了两个部分,上面是类的内存分布,下面是虚函数表。这里的编译环境VS2015。从结果可知,编译器是将虚表指针{vfptr}放在类内存的开始处,从图可知其偏移量为0,接着放置类其他成员变量,从图可知,Base_1和Base_2的偏移量分别为4,8。下面部分是类的虚函数表。在&Base_meta后面的0表示,这张虚函数表对应的虚指针{vfptr}在类内存中的分布。接着下面列出类的虚函数,虚函数左边表示虚函数的号。
1.3 单一非虚继承。
#include<iostream>
using namespace std;
class Base
{
public:
int Base_1;
int Base_2;
public:
void func1();
virtual void func2(){};
virtual void func3(){};
};
class D :public Base{
public:
int d_1;
public:
virtual void func2(){};
};
int main()
{
return 0;
}
这次使用/d1 reportSingleClassLayoutBase 只能得到与1.2相同的结果:
1> class Base size(12):
1> +---
1> 0 | {vfptr}
1> 4 | Base_1
1> 8 | Base_2
1> +---
1>
1> Base::$vftable@:
1> | &Base_meta
1> | 0
1> 0 | &Base::func2
1> 1 | &Base::func3
1>
1> Base::func2 this adjustor: 0
1> Base::func3 this adjustor: 0
在vs属性中使用命令行参数替换/d1 reportSingleClassLayoutBase :
/d1 reportAllClassLayout
运行结果(下面的结果是从很长的输出结果中找到的,输出结果太多,比较难找):
1> class Base size(12):
1> +---
1> 0 | {vfptr}
1> 4 | Base_1
1> 8 | Base_2
1> +---
1>
1> Base::$vftable@:
1> | &Base_meta
1> | 0
1> 0 | &Base::func2
1> 1 | &Base::func3
1>
1> Base::func2 this adjustor: 0
1> Base::func3 this adjustor: 0
1>
1> class D size(16):
1> +---
1> 0 | +--- (base class Base)
1> 0 | | {vfptr}
1> 4 | | Base_1
1> 8 | | Base_2
1> | +---
1> 12 | d_1
1> +---
1>
1> D::$vftable@:
1> | &D_meta
1> | 0
1> 0 | &D::func2
1> 1 | &Base::func3
1>
1> D::func2 this adjustor: 0
分析:
派生类继承了基类的全部,包括虚表指针{vfptr}和其他变量。同时将继承父类的东西放在类内存的开始处,这样,{vfptr}的地址偏移为0,同时派生类也维护一个自己的虚函数表。总结:在非虚继承下:
(1) 派生类会继承基类的全部,包括虚基指针。
(2) 派生类和基类会各自维护一个虚函数表,他们不相同,不是同一张表。从以上结果体现为,基类虚表为Base::$vftable,派生类的虚表为D::vftable。但是他们虚表对应的虚指针是一样的。
1.4 单一虚继承。
#include<iostream>
using namespace std;
class Base
{
public:
int Base_1;
int Base_2;
public:
void func1();
virtual void func2(){};
virtual void func3(){};
};
class D :virtual public Base{
public:
int d_1;
public:
virtual void func2(){};
};
int main()
{
return 0;
}
运行结果:
1> class Base size(12):
1> +---
1> 0 | {vfptr}
1> 4 | Base_1
1> 8 | Base_2
1> +---
1>
1> Base::$vftable@:
1> | &Base_meta
1> | 0
1> 0 | &Base::func2
1> 1 | &Base::func3
1>
1> Base::func2 this adjustor: 0
1> Base::func3 this adjustor: 0
1>
1> class D size(20):
1> +---
1> 0 | {vbptr}
1> 4 | d_1
1> +---
1> +--- (virtual base Base)
1> 8 | {vfptr}
1> 12 | Base_1
1> 16 | Base_2
1> +---
1>
1> D::$vbtable@:
1> 0 | 0
1> 1 | 8 (Dd(D+0)Base)
1>
1> D::$vftable@:
1> | -8
1> 0 | &D::func2
1> 1 | &Base::func3
1>
1> D::func2 this adjustor: 8
1> vbi: class offset o.vbptr o.vbte fVtorDisp
1> Base 8 0 4 0
1>
派生类继承了基类的全部,包括虚表指针{vfptr}和其他变量。但是不是将继承父类的东西放在类内存的开始处。在类内存中开始出放置的是自己的虚指针{vbptr},接着自己的成员变量,最后再放置从基类派生下来的数据。此时,派生类有了2个虚指针,一个是自己的虚指针,另一个是从基类派生下来的虚基指针,对应就有2张虚表。各个虚表下面的数字就表示其在类内存中的偏移量。总结:在虚继承下:
- (1) 派生类会继承基类的全部,包括虚基指针。但是不是将其放在类内存的地址偏移0处。派生类会新生成虚指针,放在类内存地址偏移量为0处。
- (2) 派生类有2个虚指针,对应有两张虚函数表。
参考:C++类内存分布