内存对齐原因和规则
结构体中的元素在内存中并不是紧密排列的,原因在于CPU从内存取数据时,是一次取多个字节的,可以将内存看做多个抽屉,如每个抽屉中存放4字节,此时如果要取一个4字节的int值,如果该int值不在一个抽屉中而是一个抽屉里各存放一部分字节(即没有内存对齐),就需要读取两次内存内容(开两次抽屉)才能取到完整的int值,对性能有影响,甚至有些硬件架构下,不能访问任意地址的数据,只能从某些地址下才能访问特定类型的数据,否则会报错。
对齐规则1:结构体中的每个成员必须存放在该成员大小的整数倍偏移处。
对齐规则2:结构体大小必须是其最大元素的整倍数。
为了对齐而空出来的字节填0。
如以下结构体:
struct s1 {
char c1; // char大小为1字节,需要存放在1的整数倍处,即可存放在任意地址处,此处的c1存放在偏移量0处
int i1; // int大小为4字节,因此需要存放在4的整数倍处,因此c1和i1之间有3字节的0填充,此处的i1存放在偏移量4处
char c2; // char大小为1字节,需要存放在1的整数倍处,即可存放在任意地址处,此处的c2存放在偏移量8处
int i2; // int大小为4字节,因此需要存放在4的整数倍处,因此c2和i2之间有3字节的0填充,此处的i1存放在偏移量12处
char c3; // char大小为1字节,需要存放在1的整数倍处,即可存放在任意地址处,此处的c3存放在偏移量16处
// c3后需要将结构体大小补齐到最大元素整数倍,未补齐时结构体大小为17字节,结构体中最大元素大小为4,因此结构体最后需要补3字节的0到20字节
};
以上结构体在内存中的大小是20字节而非简单的元素大小之和(11字节)。
结构体中的元素顺序不同,结构体在内存中的大小也不同:
struct s2 {
char c1; // char大小为1字节,需要存放在1的整数倍处,即可存放在任意地址处,此处的c1存放在偏移量0处
char c2; // char大小为1字节,需要存放在1的整数倍处,即可存放在任意地址处,此处的c2存放在偏移量1处
char c3; // char大小为1字节,需要存放在1的整数倍处,即可存放在任意地址处,此处的c3存放在偏移量2处
int i1; // int大小为4字节,因此需要存放在4的整数倍处,因此c3和i1之间有1字节的0填充,此处的i1存放在偏移量4处
int i2; // int大小为4字节,因此需要存放在4的整数倍处,此处的i1存放在偏移量8处
};
以上交换元素顺序后的结构体大小为12字节。
我们可以使用预编译命令#pragma pack(n)
来将对齐模数设为n,对齐模数指的是:如果是int数据,则对齐模数是4,如果是double数据,则对齐模数是8。如果将结构s1的对齐模数设为2:
#pragma pack(2)
struct s3 {
char c1; // c1存放在偏移量0处
int i1; // int大小为4字节,对齐模数为2,i1会存放在地址2处,c1和i1之间只有1字节的0填充
char c2; // c2存放在偏移量6处
int i2; // int大小为4字节,对齐模数为2,i1会存放在地址8处,c2和i2之间只有1字节的0填充
char c3; // c3存放在偏移量12处
// c3后需要将结构体大小补齐到对齐模数的整数倍处,未补齐时结构体大小为13字节,因此结构体最后需要补1字节的0到14字节
};
注意#pragma pack(n)
的目的是让结构体更紧凑,如果n比默认应该空余的字节数要大,则按应该空余的字节数来计算,如:
#pragma pack(8)
struct s4 {
char c; // c存放在偏移量0处
int i; // i如果按pack的参数来计算,应该存在偏移量8处,会填充7个0字节,超出了默认填充3个0字节,因此会填充3个0字节
};
以上结构体的大小为8而非16。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)