本文介绍 内存对齐(Memory Alignment) 的概念、为何需要对齐、结构体内存对齐 的规则与示例,以及在 iOS/ARM64 下的典型约定。与「内存五大分区」中数据在栈、堆、全局区的布局密切相关,见 01-主题|内存管理@iOS-内存五大分区。
一、什么是内存对齐
1.1 定义
- 内存对齐:数据在内存中的起始地址满足一定约束,通常是「地址为自身所占字节数的整数倍」(或按平台规定的对齐值)。
- 例如:4 字节的
int在多数平台上需** 4 字节对齐**(地址为 4 的倍数);8 字节的double需 8 字节对齐(地址为 8 的倍数)。
1.2 为什么需要对齐
| 原因 | 说明 |
|---|---|
| CPU 访问效率 | 许多 CPU 对未对齐访问有性能惩罚或需多次总线访问;对齐后可按固定步长、单次或更少次数访问。 |
| 硬件与 ABI 要求 | ARM、x86 等架构对某些类型有对齐要求;未对齐访问在部分平台可能触发异常(如 ARM 未对齐访问可配置为 fault)。 |
| 以空间换时间 | 通过填充(padding) 满足对齐,会多占一些字节,但换来稳定、高效的访问。 |
二、基本类型的对齐(典型值)
以下为 64 位 iOS/ARM64 下常见类型的典型对齐与大小(具体以 ABI 与编译器为准):
| 类型 | 大小(字节) | 典型对齐(字节) |
|---|---|---|
| char / bool | 1 | 1 |
| short | 2 | 2 |
| int | 4 | 4 |
| long / 指针(64 位) | 8 | 8 |
| float | 4 | 4 |
| double | 8 | 8 |
| long double | 8 或 16 | 8 或 16 |
平台约定:iOS 64 位(ARM64)下,编译器常采用 8 字节 作为结构体整体对齐的上限之一(即结构体大小与起始地址常为 8 的倍数);32 位下多为 4 字节。
三、结构体内存对齐规则
3.1 三条常见规则
- 成员对齐:结构体第一个成员的偏移为 0;后续成员的起始偏移 = 该成员自身对齐值的整数倍,不足则插入 padding。
- 嵌套结构体:若成员是结构体,该成员的起始偏移 = 其内部最大成员对齐值的整数倍(即嵌套结构体按自身「最严格」对齐要求对齐)。
- 整体对齐:结构体的总大小 = 其内部最大成员对齐值的整数倍;末尾不足则补足,以便结构体数组时每个元素仍对齐。
3.2 流程图:计算结构体布局(伪流程)
flowchart TB
A[遍历每个成员] --> B[当前偏移 是 该成员对齐的整数倍?]
B -->|否| C[补 padding 到满足]
B -->|是| D[放置该成员]
C --> D
D --> E[偏移 += 成员大小]
E --> A
F[所有成员放完] --> G[总大小 是 最大成员对齐的整数倍?]
G -->|否| H[末尾补 padding]
G -->|是| I[得到 sizeof]
H --> I
四、示例:结构体大小与 padding
4.1 C / Objective-C 示例
// 假设 64 位:指针 8 字节、int 4 字节、char 1 字节
struct Example1 {
double a; // 8 字节,偏移 0,[0-7]
char b; // 1 字节,偏移 8,[8]
int c; // 4 字节,需 4 对齐,故偏移 12,[12-15]
short d; // 2 字节,偏移 16,[16-17]
}; // 最大成员对齐 8,总大小需 8 的倍数:18 → 24,末尾补 6 字节
// sizeof(Example1) == 24
| 成员 | 大小 | 对齐 | 起始偏移 | 说明 |
|---|---|---|---|---|
| a | 8 | 8 | 0 | 第一个成员 |
| b | 1 | 1 | 8 | 无 padding |
| c | 4 | 4 | 12 | 偏移 9、10、11 不满足 4 对齐,补 3 字节 |
| d | 2 | 2 | 16 | 无 padding |
| (尾部) | — | — | 18→24 | 总大小凑成 8 的倍数 |
4.2 成员顺序对大小的影响
同一批成员、顺序不同会导致 padding 不同,从而总大小不同:
struct Compact {
double a; // 0-7
int b; // 8-11
int c; // 12-15
char d; // 16
}; // 总大小 17 → 对齐 8 → 24 字节(末尾补 7)
struct Sparse {
char a; // 0
double b; // 需 8 对齐 → 8-15,前补 7
int c; // 16-19
}; // 总大小 20 → 对齐 8 → 24 字节
实践建议:若需节省结构体占用,可将大类型放前、小类型集中,减少中间 padding。
五、Swift 中的内存布局与对齐
5.1 MemoryLayout
- MemoryLayout<T>.size:类型 T 的实际占用字节数(不含尾部为数组元素对齐而留的 padding)。
- MemoryLayout<T>.stride:在连续存储(如数组)中,相邻两个 T 的起始地址之差,即「对齐后的大小」。
- MemoryLayout<T>.alignment:类型 T 的对齐要求(字节数)。
5.2 示例
struct SHPerson {
var age: Int // 8 字节
var weight: Int // 8 字节
var sex: Bool // 1 字节
}
// size = 17(实际成员占用)
// stride = 24(8 字节对齐后,用于数组等)
// alignment = 8
六、与内存五大分区的关系
- 栈、堆、全局区中存放的局部变量、对象、全局/静态变量,其起始地址与内部成员都受对齐约束;编译器与运行时在分配时会保证对齐。
- 理解对齐有助于:估算结构体/类实例占用、排查「sizeof 与预期不符」、与 C 互操作或做底层布局时避免未对齐访问。
七、自定义对齐与 packed(简述)
| 手段 | 说明 |
|---|---|
| _attribute_((aligned(n))) | 指定变量或结构体按 n 字节对齐(如缓存行 64 字节)。 |
| _attribute_((packed)) | 取消结构体内部 padding,成员紧挨排列;可减小体积但可能未对齐,访问效率或安全性下降,需谨慎使用。 |
八、小结(思维导图)
mindmap
root((内存对齐))
目的
CPU 访问效率
ABI 与硬件要求
规则
成员按自身对齐
整体大小为最大对齐的整数倍
iOS/ARM64
常用 8 字节整体对齐
size 与 stride
实践
成员顺序影响大小
packed / aligned 慎用
参考文献
- ARM Architecture Reference Manual(对齐与未对齐访问)
- Swift - MemoryLayout
- ABI 文档:ARM64 下 iOS/macOS 的 C / Objective-C 类型大小与对齐