字节对齐规则总结(新手笔记)
1. 什么是字节对齐?
- 定义:数据在内存中的起始地址必须是某个值的整数倍(如4、8等)。
- 目的:提高CPU访问内存的效率,避免硬件错误(如ARM处理器不支持未对齐访问)。
2. 对齐规则
(1) 基本数据类型的对齐
每个类型的起始地址必须是自身大小的整数倍:
char
(1字节) → 任意地址。short
(2字节) → 地址是2的倍数(如0x1002
,0x1004
)。int
(4字节) → 地址是4的倍数(如0x1004
,0x1008
)。double
(8字节) → 地址是8的倍数(如0x1008
,0x1010
)。- 指针(64位系统):占8字节,对齐到8的倍数。
(2) 结构体成员的对齐
-
规则:每个成员起始地址满足以下较小值:
- 成员自身大小;
- 编译器默认对齐系数(通常为4或8)。
-
示例:
C struct Example { char a; // 占1字节 → 对齐到1的倍数(地址0) int b; // 占4字节 → 对齐到4的倍数(地址4) };
char a
后填充3字节,使int b
从地址4开始。- 结构体总大小:1(a) + 3(填充) + 4(b) = 8字节。
(3) 结构体整体对齐
-
规则:结构体的总大小必须是以下值的整数倍:
- 结构体内最大成员的对齐值;
- 编译器默认对齐系数。
- 取两者的较小值。
-
示例:
C struct Test { char a; // 1字节,对齐要求1 double b; // 8字节,对齐要求8 };
char a
后填充7字节,double b
从地址8开始。- 结构体总大小:1(a) + 7(填充) + 8(b) = 16字节(满足8的倍数)。
(4) 数组的对齐
- 规则:数组元素按基本类型对齐规则连续排列。
- 示例:
int arr[3]
占用12字节(3×4字节),起始地址是4的倍数。
3. 对齐问题示例
示例1:顺序影响空间
C
// 顺序优化前(占用12字节)
struct Bad {
char a; // 地址0
// 填充3字节
int b; // 地址4
char c; // 地址8
// 填充3字节(结构体总大小需是4的倍数)
};
// 顺序优化后(占用8字节)
struct Good {
int b; // 地址0
char a; // 地址4
char c; // 地址5
// 填充2字节(总大小8字节,满足4的倍数)
};
示例2:跨平台差异
- 32位系统指针占4字节,64位系统占8字节 → 结构体大小可能不同。
4. 如何手动控制对齐?
(1) 编译器指令
-
#pragma pack(n)
:强制编译器按n
字节对齐。C #pragma pack(1) // 关闭填充,按1字节对齐 struct Tight { int a; // 地址0 char b; // 地址4(无需填充) }; // 总大小5字节 #pragma pack() // 恢复默认对齐
(2) 强制取消填充(GCC)
C
struct __attribute__((packed)) Unaligned {
int a; // 地址0
char b; // 地址4
}; // 总大小5字节
5. 常见误区
- 误区1:对齐系数越大越好 → 可能浪费内存。
- 误区2:所有平台对齐规则相同 → 实际需考虑硬件和编译器差异。
6. 总结
核心规则 | 说明 |
---|---|
基本类型对齐 | 地址是自身大小的整数倍 |
结构体成员对齐 | 起始地址取自身大小和编译器系数的较小值 |
结构体整体对齐 | 总大小是最大成员对齐值的整数倍 |
手动控制对齐 | #pragma pack 或编译器属性 |
优化技巧:
- 按成员大小降序排列结构体(大成员在前)→ 减少填充。
- 跨平台代码慎用强制对齐 → 可能引发兼容性问题[。]