内存对齐是C++中一种优化内存访问效率的机制,指的是编译器在分配内存时,会让数据的内存地址满足特定的倍数要求,而不是按照数据的实际大小连续排列。这种机制虽然可能会增加部分内存开销,但能大幅提升CPU读取内存的速度,是底层开发中需要掌握的基础内容。

内存对齐的核心原因
CPU访问内存时并不是逐个字节读取的,而是按照固定的字长读取,比如32位CPU一次读取4字节,64位CPU一次读取8字节。如果数据存储的地址没有对齐,比如一个4字节的int类型数据存储在地址1的位置,CPU需要读取两次内存才能拿到完整数据,还会做额外的拼接操作,效率会明显下降。内存对齐就是让数据的起始地址是自身大小或者编译器指定对齐值的整数倍,减少不必要的内存访问次数。
默认对齐规则
不同编译器的默认对齐规则基本一致,通常遵循以下逻辑:
- 结构体的起始地址对齐到最大成员大小或者编译器默认对齐值(通常是4或8字节)的整数倍
- 每个成员的起始地址对齐到自身大小的整数倍,如果自身大小超过编译器默认对齐值,则对齐到默认对齐值的整数倍
- 结构体的总大小对齐到最大成员大小或者编译器默认对齐值的整数倍,不足的部分会填充空白字节
代码示例验证对齐效果
我们可以通过sizeof运算符查看不同结构体的大小,验证对齐规则的实际效果,以下是测试代码:
#include <iostream>
using namespace std;
// 结构体1:成员按大小升序排列
struct Test1 {
char c; // 1字节
int i; // 4字节
char c2; // 1字节
};
// 结构体2:成员按大小降序排列
struct Test2 {
int i; // 4字节
char c; // 1字节
char c2; // 1字节
};
int main() {
cout << "Test1大小: " << sizeof(Test1) << endl;
cout << "Test2大小: " << sizeof(Test2) << endl;
return 0;
}
在默认对齐值为4字节的编译器下,运行结果通常是Test1大小为12字节,Test2大小为8字节。Test1中char c占1字节后,int i需要对齐到4字节地址,所以c后面填充3个空白字节,i占4字节后,c2占1字节,最后结构体总大小需要对齐到4的倍数,所以c2后面再填充3个空白字节,总大小就是1+3+4+1+3=12。而Test2中int i先占4字节,两个char共占2字节,后面填充2个空白字节让总大小到4的倍数,总大小就是4+2+2=8。
修改对齐方式
如果我们需要减少内存占用,或者对接某些需要特定内存布局的协议,可以通过编译指令修改对齐值。不同编译器的指令略有区别,常见的方式如下:
GCC/Clang编译器
使用__attribute__((aligned(n)))指定对齐值,或者使用__attribute__((packed))取消对齐,按1字节紧凑排列:
#include <iostream>
using namespace std;
// 取消对齐,按1字节排列
struct __attribute__((packed)) PackedStruct {
char c;
int i;
char c2;
};
int main() {
cout << "紧凑结构体大小: " << sizeof(PackedStruct) << endl; // 输出6
return 0;
}
MSVC编译器
使用#pragma pack(n)指令设置对齐值,#pragma pack()恢复默认对齐:
#include <iostream>
using namespace std;
#pragma pack(1) // 设置对齐值为1字节
struct PackedStruct {
char c;
int i;
char c2;
};
#pragma pack() // 恢复默认对齐
int main() {
cout << "紧凑结构体大小: " << sizeof(PackedStruct) << endl; // 输出6
return 0;
}
内存对齐的注意事项
取消对齐或者修改对齐值虽然能减少内存占用,但会带来性能损失,甚至在某些架构下会导致程序崩溃,比如ARM架构的CPU访问未对齐的内存会直接触发硬件异常。所以在编写代码时,除非有明确的场景需求,否则不要随意修改默认的对齐规则,尽量合理安排结构体成员的顺序,把大小相同的成员放在一起,减少填充字节的浪费。