08-主题|内存管理@iOS-内存对齐

3 阅读5分钟

本文介绍 内存对齐(Memory Alignment) 的概念、为何需要对齐、结构体内存对齐 的规则与示例,以及在 iOS/ARM64 下的典型约定。与「内存五大分区」中数据在栈、堆、全局区的布局密切相关,见 01-主题|内存管理@iOS-内存五大分区


一、什么是内存对齐

1.1 定义

  • 内存对齐:数据在内存中的起始地址满足一定约束,通常是「地址为自身所占字节数的整数倍」(或按平台规定的对齐值)。
  • 例如:4 字节的 int 在多数平台上需** 4 字节对齐**(地址为 4 的倍数);8 字节的 double8 字节对齐(地址为 8 的倍数)。

1.2 为什么需要对齐

原因说明
CPU 访问效率许多 CPU 对未对齐访问有性能惩罚或需多次总线访问;对齐后可按固定步长、单次或更少次数访问。
硬件与 ABI 要求ARM、x86 等架构对某些类型有对齐要求;未对齐访问在部分平台可能触发异常(如 ARM 未对齐访问可配置为 fault)。
以空间换时间通过填充(padding) 满足对齐,会多占一些字节,但换来稳定、高效的访问。

二、基本类型的对齐(典型值)

以下为 64 位 iOS/ARM64 下常见类型的典型对齐与大小(具体以 ABI 与编译器为准):

类型大小(字节)典型对齐(字节)
char / bool11
short22
int44
long / 指针(64 位)88
float44
double88
long double8 或 168 或 16

平台约定:iOS 64 位(ARM64)下,编译器常采用 8 字节 作为结构体整体对齐的上限之一(即结构体大小与起始地址常为 8 的倍数);32 位下多为 4 字节。


三、结构体内存对齐规则

3.1 三条常见规则

  1. 成员对齐:结构体第一个成员的偏移为 0;后续成员的起始偏移 = 该成员自身对齐值的整数倍,不足则插入 padding
  2. 嵌套结构体:若成员是结构体,该成员的起始偏移 = 其内部最大成员对齐值的整数倍(即嵌套结构体按自身「最严格」对齐要求对齐)。
  3. 整体对齐:结构体的总大小 = 其内部最大成员对齐值的整数倍;末尾不足则补足,以便结构体数组时每个元素仍对齐。

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
成员大小对齐起始偏移说明
a880第一个成员
b118无 padding
c4412偏移 9、10、11 不满足 4 对齐,补 3 字节
d2216无 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 慎用

参考文献