c++类的内存布局简单分析
- 首先要分析c++类的内存布局,我们应该找到能够直观的看到编译器生成的类的内存分布情况,因为我使用的是VS进行开发的,所以使用VS自带的功能进行查看。步骤只有一步,就是在命令行中添加
/d1 reportSingleClassLayoutA
,这样就可以在代码编译时,看到类的内存分布情况。
2.下面我们写一个简单的类,看以下它的内存分布
#include "stdafx.h"
#include <iostream>
#include <inttypes.h> //由于x86和x64的区别,我们在使用int进行指针转换的时候,
//会出问题,所以需要引入该文件对此情况进行处理
using namespace std;
#pragma pack(8) //防止由于字节对齐不一致,产生的内存布局不一致的问题
class A
{
public:
void add() {} //成员函数会在存在代码区,不会在类内分布
static int bd; //静态成员变量会在静态区(也又称为全局区的),不会在类内分布
static void sum() {} //静态成员函数会在代码区,不会在类内分布
//画重点
//一旦出现虚函数,在类的内存分布一开始就必须是一个一个指向虚表指针
//该虚表维护着该类自己的虚函数的函数指针,存储的顺序和声明的顺序一直。
//当然虚表本身不占据类的内存布局
//虚函数本身不占据内存的分布,也是在代码区
virtual int aaa(int a, int b) {
cout << a + b << endl;
return a + b; }
virtual int ddd() { cout << 6666 << endl; return 0; }
//成员变量也是顺序和声明顺序是一致的在类内进行排列
int a;
double b;
};
//注意这里需要加上_stdcall,要不会出问题,但是调用顺序是__cdecl,
//猜测c++底部通过虚表存储的函数指针的调用也是通过_stdcall的方式
typedef void(__stdcall *fun)(int,int);
typedef void(__stdcall *fun1)(void);
int main()
{
A a;
//uintptr_t 大家可以堪称就是unit,是为了防止在64位和32位的差别
//下来,分析下这个调用的方式
//1.首先(uintptr_t*)(&a)因为这个有虚函数,所以类的首地址就是指向虚函数表的指针的首地址,
//这就是取到这个虚函数指针的地址了
//2.*(uintptr_t*)(&a) 取到这个虚函数指针的值,就是虚表的位置
//3.(uintptr_t*)*(uintptr_t*)(&a)+0 拿到虚表中第一个元素(函数指针)的位置
//4.*((uintptr_t*)*(uintptr_t*)(&a)+0) 取到第一个函数指针的值,就是代码区中函数的存放位置
//5.(fun) *((uintptr_t*)*(uintptr_t*)(&a)+0) 转换成对应的函数指针类型
//6.pfun(2,3);通过该函数指针调用函数实现,所以该句调用的就是函数aaa
fun pfun = (fun) *((uintptr_t*)*(uintptr_t*)(&a)+0);
pfun(2,3);
fun1 pfun1 = (fun1) *((uintptr_t*)*(uintptr_t*)(&a) + 1);
pfun1();
return 0;
}
那我们在看下类的实际内存分布情况:
首先:类A占据24个字节的大小,
其次,虚函数表的指针占据了类的首地址
再次,虚表中按声明顺序存储了虚函数的函数指针
再次,成员变量按声明顺序占据了类的内存布局。
再次,静态成员不占据类的内存布局。
最后,alignment member表示的字节没对齐,系统进行的补位操作。如图就是在a和b之间补了4个字节。
3.最后在看下运行结果,结果验证正确