字节对齐

0 阅读3分钟

字节对齐规则总结(新手笔记)

1. 什么是字节对齐?

  • 定义:数据在内存中的起始地址必须是某个值的整数倍(如4、8等)。
  • 目的:提高CPU访问内存的效率,避免硬件错误(如ARM处理器不支持未对齐访问)。

2. 对齐规则

(1) 基本数据类型的对齐

每个类型的起始地址必须是自身大小的整数倍:

  • char(1字节) → 任意地址。
  • short(2字节) → 地址是2的倍数(如0x10020x1004)。
  • int(4字节) → 地址是4的倍数(如0x10040x1008)。
  • double(8字节) → 地址是8的倍数(如0x10080x1010)。
  • 指针(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或编译器属性

优化技巧

  • 按成员大小降序排列结构体(大成员在前)→ 减少填充。
  • 跨平台代码慎用强制对齐 → 可能引发兼容性问题[。]