内存布局
内存布局的基础
- 对齐(Alignment):每个数据类型(如整数、浮点数等)通常有一个对齐要求,这意味着它们必须按特定的字节边界存储,这可以提高内存访问的效率。
- 填充(Padding):为了满足对齐要求,编译器可能会在数据成员之间插入填充字节。
内存对齐规则
| 数据项 | 对齐规则 |
|---|---|
| 基本数据类型 | 地址 = sizeof(type) 的整数倍 |
| 结构体起始地址 | 地址 = max(所有成员对齐值) 的整数倍 |
| 结构体成员偏移 | 偏移量 = 该成员对齐值 的整数倍 |
| 结构体总大小 | 大小 = max(所有成员对齐值) 的整数倍 |
规则1:自身对齐值
每个基本数据类型必须存储在其大小整数倍的地址上。
char(1字节): 可存放在任何地址(因为任何数都是1的倍数)。short(2字节): 地址必须是2的倍数(偶数地址)。int(4字节): 地址必须是4的倍数。double(8字节): 地址必须是8的倍数。- 指针(通常4或8字节): 地址必须是4或8的倍数。
规则2:结构体对齐值
整个结构体的对齐值是其所有成员中最大对齐值。 结构体的起始地址必须是这个最大对齐值的整数倍。
cpp
struct Example {
char a; // 对齐值=1
int b; // 对齐值=4
double c; // 对齐值=8
}; // 该结构体的对齐值是 max(1, 4, 8) = 8规则3:结构体的大小
整个结构体的大小必须是其最大对齐值的整数倍。 编译器会在结构体末尾添加“填充字节”(Padding)来满足这一要求。
规则4:成员偏移量
结构体内部的每个成员,其相对于结构体起始地址的偏移量(Offset) 必须是该成员自身对齐值的整数倍。 编译器会在成员之间添加“填充字节”来满足这一要求。
编译指令的影响
可以使用编译指令(如GCC/Clang的 __attribute__((packed)) 或MSVC/GCC的 #pragma pack(n))来修改默认的对齐规则。
#pragma pack(n):指定新的对齐系数 n。(通常是 1、2、4、8、16)
- 每个成员的有效对齐值将变为
min(其自身对齐值,n) - 结构体总大小有效对齐值变为
min(成员中最大对齐值, n)的整数倍
类、结构体的内存布局
类和结构体的成员是按照定义的顺序排列在内存中的,但编译器可能插入填充字节保证对齐。
cpp
struct MyStruct {
char a; // 大小=1,对齐值=1
int b; // 大小=4,对齐值=4
short c; // 大小=2,对齐值=2
double d; // 大小=8,对齐值=8
};确定结构体对齐值:成员中最大对齐值是
double d的8。所以整个结构体的起始地址必须是8的倍数。从偏移量0开始放置成员:
char a:大小1,对齐值1。放在偏移量0。0 % 1 == 0,符合。int b:大小4,对齐值4。下一个可用偏移量是1。但1 % 4 != 0,不符合。需要填充3字节(偏移量1,2,3),然后将其放在偏移量4。4 % 4 == 0,符合。short c:大小2,对齐值2。下一个可用偏移量是8(4+4)。8 % 2 == 0,符合。将其放在偏移量8,占用8和9两个字节。double d:大小8,对齐值8。下一个可用偏移量是10。但10 % 8 != 0,不符合。需要填充6字节(偏移量10-15),然后将其放在偏移量16。16 % 8 == 0,符合。占用16到23字节。
计算当前总大小并最终调整:
- 最后一个成员
d结束于偏移量23。 - 当前总大小为24字节。
- 检查总大小是否为最大对齐值(8)的整数倍?
24 % 8 == 0,符合。无需在末尾额外填充。
- 最后一个成员