C++类的内存布局

本文探讨了C++中结构体与类的内存布局规则,包括VS2022环境下结构体的对齐策略和类的首地址特性。同时,详细分析了带有虚函数的类在内存中的布局,指出虚函数会引入虚函数表(vfptr),增加额外的存储开销。此外,还讨论了虚函数类及其子类的内存分布,展示了虚函数表在多态调用中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、C++结构体与类布局基本规则(编译器为VS2022)

C中结构体内存布局基本规则如下:

  • 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
  • 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
  • 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。

  C++中类的起始地址似乎没有遵守第一条规则,目前测试发现其首地址总是4的倍数。类对象的地址规定为类中第一个非静态变量的起始地址,因为静态变量不属于任何一个对象,它存放在全局区。

#include<iostream>
using namespace std;
class Animal {
public:
	static int i;
	int a=5;
	double b;
	void func1() {}
	void func2() {}
};
int Animal::i = 5;
int main() {
	Animal ani;
	cout << &ani << endl; //0053FDB4
	cout << &Animal::i << endl; //0058A000
	cout << &ani.a << endl; //0053FDB4
	cout << sizeof(Animal); //16,
	return 0;
}

  通过以下代码发现,C++中类的内存布局经过测试发现是完全遵守第二条规则的。

#include<iostream>
using namespace std;
class Animal {
public:
	bool b_;
	int a=5;
	double b;
	void func1() {}
	void func2() {}
};
int main() {
	Animal ani;
	cout << sizeof(ani.b_) << endl; //1
	cout << &ani.b_ << endl; //00BBF794
	cout << &ani.a << endl; //00BBF798
	cout << &ani.b << endl; //00BBF79C
	return 0;
}

  通过以下代码发现,C++中类的内存布局经测试发现是严格遵守第三条规则的。

#include<iostream>
using namespace std;
class Animal {
public:
	bool b_;
	int a=5;
	double b;
	void func1() {}
	void func2() {}
};
int main() {
	Animal ani;
	cout << sizeof(ani) << endl; //16
	return 0;
}

2、C++中带有虚函数的类布局

(1)观察C++中一个普通类的布局:

#include<iostream>
using namespace std;
class Animal {
public:
	bool b_;
	int a=5;
	double b;
	void func1() {}
	void func2() {}
	void func3() {}
	void func4() {}
	void func5() {}
	
};
int main() {
	Animal ani;
	cout << sizeof(ani) << endl; //16
	return 0;
}

使用VS2022开发工具Developer PowerShell for VS 2019,在对应文件夹下输入命令cl /d1 reportSingleClassLayout类名 文件名.cpp,可以得到类Animal的内存结构图示:
在这里插入图片描述

图1:普通类的内存描述

左边0、4、8表示属性相对起始位置的偏移字节数,<alignment member>(size=3) 表示为了对其而占用的三个字节。

(2)带有虚函数的类内存布局。

#include<iostream>
using namespace std;
class Animal {
public:
	bool b_;
	int a=5;
	double b;
	void func1() {}
	virtual void func2() {}
	virtual void func3() {}
	virtual void func4() {}
	virtual void func5() {}
};
int main() {
	Animal ani;
	cout << sizeof(ani) << endl; //24
	return 0;
}

在这里插入图片描述

图2:带虚函数类的内存描述

可以看到带有虚函数的类所占空间多了8个字节,这是因为在类起始位置多了vfptr(虚函数(表)指针),它指向虚函数表(vftable);vftable内部记录了虚函数的入口地址。

(3)虚函数类的子类内存布局。

#include<iostream>
using namespace std;
class Animal {
public:
	bool b_;
	int a=5;
	double b;
	void func1() {}
	virtual void func2() {}
	virtual void func3() {}
	virtual void func4() {}
	virtual void func5() {}
	
};
class Cat :public Animal {
	virtual void func3() {}
	virtual void func4() {}
};
int main() {
	Cat cat;
	cout << sizeof(cat) << endl; //24
	return 0;
}

在这里插入图片描述

图3:虚函数子类的内存描述

可以看到在子类Cat中,父类Animal的属性被全部继承了下来,但是在子类Cat的虚函数表中可以发现子类Cat重写的函数其函数地址被换成了重写后的函数地址。当我们用一个父类Animal指针指向子类Cat对象,即Animal cat = new Cat()之后,执行cat.func2()语句,系统就会根据子类的虚函数表找到正确的子类重写后的函数。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值