内存对齐
引言
理论上,计算机可以访问任何内存地址上的数据。但实际上,系统会对变量的存放地址施加限制,通常要求变量 的首地址是某个特定值(称为对齐模数 N)的整数倍。这种限制就是内存对齐。
为什么要求内存对齐
主要原因有两个:
- 硬件平台限制:不同硬件平台对内存的存取单位可能不同。为了确保处理器能够正确存取数据,必须进行对齐。
- 提高CPU内存访问速度:处理器通常以固定的粒度访问内存。如果数据没有对齐,CPU可能需要两次内存访问才能读取完整数据;而对齐后,可以一次性读取,提高效率。
内存对齐规则
内存对齐的对象是所有数据类型 ,包括基本类型(如 int、double)、数组、结构体(struct)和类(class)。
详细说明:
- 基本类型 :每个基本类型都有自身的对齐要求。例如,一个
int(通常4字节)通常需要存储在4的整数倍地址上;double(8字节)通常需要8字节对齐。 - 结构体(struct)和类(class):在C++中,结构体和类在内存布局上本质相同(仅默认访问权限不同)。它们作为复合类型,其内存对齐规则涉及内部各成员的对齐以及整体的对齐。
- 数组 :数组的对齐要求与其元素类型一致。例如,
int数组的每个元素都按int的对齐要求存放。
基本类型的对齐要求
| 类型 | 大小(字节) | 对齐要求(字节) |
|---|---|---|
char |
1 | 1 |
short |
2 | 2 |
int |
4 | 4 |
float |
4 | 4 |
double |
8 | 8 |
long |
4(32位)/8(64位) | 4 或 8 |
| 指针 | 4/8 | 4 或 8 |
对齐要求通常等于类型的大小,但并非绝对(例如
long double可能对齐为 16 字节)。
结构体与类的对齐
默认对齐规则
- 每个成员必须放在其自身对齐要求的整数倍地址上。
- 整个结构体的大小必须是最大成员对齐要求的整数倍。
- 成员按声明顺序排列,编译器在必要时插入填充字节(padding)。
cpp
struct MyStruct {
char a; // 1字节
int b; // 4字节
double c; // 8字节
};
class MyClass {
char a;
int b;
double c;
public:
void foo() {}
};
// 在相同编译环境下,MyStruct 和 MyClass 的内存布局和对齐要求通常一致。
MyStruct/MyClass 内存布局(假设起始地址为0):
+----+----+----+----+----+----+----+----+
| a | padding | b | // 偏移0-7
+----+----+----+----+----+----+----+----+
| c | // 偏移8-15
+----+----+----+----+----+----+----+----+
详细说明:
1. char a: 偏移0,占用1字节
2. 填充: 偏移1-3,3字节,为了满足int b的4字节对齐
3. int b: 偏移4-7,4字节
4. double c: 偏移8-15,8字节,同时已经是8的倍数
总大小:16字节
使用#pragma pack对齐
某些场景(如网络协议、硬件寄存器、文件格式)需要取消填充字节 ,使结构体紧密排列。编译器提供了打包指令 #pragma pack
网络协议传输中,如果把 padding 中无意义的数据打包进入,可能导致异常
基本语法
cpp
// 设置对齐值为 n(n 通常是 1、2、4、8、16 等 2 的幂)
#pragma pack(n)
// 保存当前对齐设置并设置新值(推荐用法)
#pragma pack(push, n)
// 恢复之前保存的对齐设置
#pragma pack(pop)
// 恢复编译器默认对齐(通常是 8)
#pragma pack()
示例
cpp
// 默认对齐下,结构体大小为24字节
struct DefaultAlign {
char a; // 1字节 + 7字节填充
double b; // 8字节
int c; // 4字节 + 4字节填充(整体需是8的倍数)
}; // sizeof = 24
// 使用4字节对齐,大小减少到16字节
#pragma pack(4)
struct Pack4Align {
char a; // 1字节 + 3字节填充
double b; // 8字节(按4对齐,分两次存储)
int c; // 4字节
}; // sizeof = 16
#pragma pack()
打包的代价
- 访问非对齐成员可能引发性能下降甚至硬件异常(若平台不支持非对齐访问)。
- 某些处理器会静默修复非对齐访问,但需要额外微码操作,速度慢。
- 跨平台可移植性:同一份打包结构在不同架构上行为可能不同。
参考资料