Skip to content

内存对齐与 sizeof 计算

一、什么是内存对齐(Alignment)?

数据在内存中的起始地址应为 其类型对齐值(alignof)的整数倍。例如 int(4 字节)起始地址应当 4 的倍数,double(8 字节)应为 8 的倍数。CPU 按字访问,未对齐访问轻则慢(多次总线访问),重则硬件异常(某些平台)。

二、为什么需要对齐?

  1. 性能:现代 CPU 以「字(word,如 8 字节)」为单位从内存读数据。若 8 字节 double 跨越两个 8 字节 cache line/总线周期,需读两次拼接 → 慢。
  2. 硬件要求:部分架构(如部分 RISC/未对齐会 fault)强制对齐,否则崩溃。
  3. 原子性/可移植:对齐保证 std::atomic 等在地址上有正确字边界。

三、对齐规则(C/C++)

结构体整体大小要满足:结构体大小是其「最大成员对齐值」的整数倍(并在成员间插入 padding 令每个成员都对齐)。

手算步骤:

  1. 每个成员的偏移须为自身对齐值的倍数,不足则补 padding。
  2. 结构体总大小须为最大成员对齐值的倍数,末尾补 padding。

四、经典手算题

cpp
struct A {
    char  c;   // 1 字节,offset 0
    int   i;   // 4 字节,offset 1→补3变4(第0~3填c+3padding)
    short s;   // 2 字节,offset 8
    // 已到 10;最大对齐 4,需补到 12
};
sizeof(A) == 12
cpp
struct B {
    double d;  // 8 字节 offset 0
    char  c;   // 1 字节 offset 8
    int   i;   // 4 字节 offset 9→补3变12
    // 到16;max align 8 → 16
};
sizeof(B) == 16

顺序影响大小:成员按「从大到小 / 对齐值从高到低」声明可减少 padding:

cpp
struct C { char c; int i; char c2; }; // 1+3+4+1+3(pad) =12
struct D { int i; char c; char c2; }; // 4+1+1+2(pad)   =8   ← 更省

五、alignof / alignas / 结构体内存细节

cpp
alignof(int)            // 4
struct alignas(16) S {}; // 强制定 16 字节对齐
  • alignas 提高对齐;offsetof 取成员偏移。
  • 空 struct:C++ 空 struct/class 大小为 1(每个对象需唯一地址)。作为成员优化掉?见 EBO:空基类优化让基类不额外占位。

六、常见追问

Q1. 为什么结构体带引用/指针/虚函数会增加对齐?

  • 引用成员、指针:sizeof = 指针大小(8),对齐 = 8。
  • 虚函数:开头多一个 vptr(8 字节),相当于对象首成员对齐 8。

Q2. #pragma pack 是什么?

#pragma pack(1) 取消自动填充 / #pragma pack(4) 指定对齐,用于网络协议、文件头、跨平台结构(节省、匹配特定二进制布局)。代价:该结构若被写成非对齐地址访问可能变慢/不可移植。

cpp
#pragma pack(push, 1)
struct NetHeader { char type; int len; };  // 大小5(正好连续)
#pragma pack(pop)

Q3. 对齐和缓存行(cache line)关系?

cache line(×86 通常 64 字节):假共享 false sharing(相邻缓存行冲突)。alignas(64) 让热点数据独占 64 字节可避免(多线程写相邻变量时的性能问题)。

Q4. malloc 返回的地址对齐吗?

保证能存放任何标准类型的地址(一般 16 字节对齐给 long double/vector 等),所以 new/malloc 出来的对象天然对齐。

七、实战提醒

手写容器/内存池时若要用自定义原始 buffer 存多种类型,需 alignas(max_align_t)alignof(T) 处理,否则 UB——这也是手写 vector/optional 常常用 alignas + placement new 的原因。

C++ 面试八股 · VitePress 版