一、内存对齐的基本原则
1、成员对齐规则
每个成员的起始地址必须是其类型大小的整数倍(或指定对齐值的整数倍)
例如:
- char(1字节)可以放在任意地址
- short(2字节)必须从偶地址开始(如0x0000、0x0002)
- int(4字节)必须从4的倍数地址开始(如0x0000、0x0004)
- double(8字节)必须从8的倍数地址开始
2、结构体整体对齐规则
结构体的总大小必须是最宽成员类型大小的整数倍(或指定对齐值的整数倍)
如果结构体包含嵌套结构体,嵌套结构体的对齐要求以内部结构体的最大对齐值为准
二、内存对齐示例
示例1:基础对齐
struct Example1 {
char a; // 1字节(地址0x0000)
// 填充3字节(0x0001~0x0003)
int b; // 4字节(地址0x0004)
// 总计:8字节(1 + 3 + 4)
};
原因:int需要3字节对齐,因此a后填充3字节,使b从地址4开始
总大小:sizeof(struct Example1) = 8
示例2:调整成员顺序
struct Example2 {
int b; // 4字节(地址0x0000)
char a; // 1字节(地址0x0004)
// 填充3字节(0x0005~0x0007)
};
原因:调整顺序后,a后的填充减少,但结构体总大小仍需对齐到4字节的倍数
总大小:sizeof(struct Example2) = 8
示例3:嵌套结构体
struct Example3 {
char a; // 1字节(地址0x0000)
// 填充3字节(0x0001~0x0003)
struct Example1 inner; // 最宽成员为int(4字节)
// inner的起始地址为0x0004
};
原因:Example1结构体内的最大对齐值为int,即4字节
总大小:sizeof(struct Example3) = 12(1+3+8)
三、控制对齐方式
1、使用#pragma pack(n)指定对齐值
通过预编译指令#pragma pack(n)可以显式指定对齐方式:
#pragma pack(1) // 禁用对齐填充
struct Packed {
char a;
int b;
};
#pragma pack() // 恢复默认对齐
- 结果:sizeof(struct Packed) = 5 (1 + 4)
- 注意:此方式可能导致性能下降或硬件异常(如ARM平台不允许未对齐访问)
2、使用offsetof宏查看偏移量
标准库<stddef.h>中的offsetof(type, number)可以计算成员相对于结构体起始地址的偏移:
#include <stddef.h>
offsetof(struct Example1, b); // 返回4
四、内存对齐的原因
1、硬件限制
- 某些处理器(如ARM)不允许访问未对齐的内存地址,强行访问会出发硬件异常
- x86/x64平台虽然支持未对齐内存访问,但效率较低
2、性能优化
- 对齐的内存,访问时可以减少CPU访问次数(如读取4字节对齐的int只需要一次访问,未对齐可能需要两次)
- 缓存行对齐可以减少缓存冲突