内存对齐
内存对齐是 编译器自动安排结构体成员在内存中的存放位置,并且在成员之间、结构体末尾自动插入填充字节(padding),让每个成员的起始地址满足特定倍数要求
规则
- 第⼀个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对⻬到对⻬数(对⻬数 = 编译器默认的⼀个对⻬数与该成员⼤⼩的较⼩值)的整数倍的地址处。
- 如果结构体里面嵌套另一个结构体,内部结构体的对齐单位 = 内部结构体最大基础成员的大小。
- 结构体最后的总大小,必须是整个结构体最大基础成员大小的整数倍。成员排完不够倍数,在结构体末尾补 padding。
- 在 vs 中,默认对齐数是 8。
例子
以下例子在 vs 2022 编译器下执行:
structA{inta;charb;charc;};structB{charb;inta;charc;};printf("%zu\n",sizeof(A));// 结果为 8printf("%zu\n",sizeof(B));// 结果为 12结构体 A 和 B 拥有完全相同的成员变量,但是计算出来的内存大小却不同。
分析:
- A 和 B 的成员变量类型为 int(4字节),char (1字节),4 > 1 ,故最大成员的大小为4字节;
- vs 默认对齐数为 8 字节,8 > 4 ,故结构体的对齐数为4字节;
- 结构体 A中,成员 a 为 int ,占用地址偏移量从 0 到 3,成员 b 为 char,占用地址偏移量 为 4,同理成员 c 为 5,为了符合对齐数 4字节的整数倍,结构体末尾补上 2字节,则整个结构体的偏移量为 0 到 7 共 8 字节;
// 示意图01234567a a a a b c \ \- 结构体 B中,成员 b 为 char ,占用地址偏移量为 0 ,成员 a 为 int,为了对⻬到对⻬数⼤⼩的整数倍的地址处,b 后面补上 3字节,则 a 占用地址偏移量 为 4 到 7,同理得成员 c 为 8,为了符合对齐数 4字节的整数倍,结构体末尾补上 3字节,则整个结构体的偏移量为 0 到 11 共 12 字节;
// 示意图01234567891011b \ \ \ a a a a c \ \ \为什么要有内存对齐
内存对齐的出现是为了适配CPU的访问规则,提升内存读写效率。CPU访问内存不是单字节读取的,而是按照固定块(2/4/8 字节,叫访问粒度)读取。
比如CPU一次性读取 4字节时,如果我们没有内存对齐规则地去访问结构体:
structA{charb;inta;charc;};// 假设没有内存对齐的示意图01234567b a a a a c这里成员 a 跨了两个内存块,CPU 就需要读两次内存,再拼接、裁剪,速度变慢。
内存对齐好处和坏处
- 好处:CPU 读写更快,兼容更多硬件。
- 坏处:填充字节 padding,结构体占用内存变大。
内存对齐是空间换时间的典型例子。