Appearance
内存对齐与 sizeof 计算
一、什么是内存对齐(Alignment)?
数据在内存中的起始地址应为 其类型对齐值(alignof)的整数倍。例如 int(4 字节)起始地址应当 4 的倍数,double(8 字节)应为 8 的倍数。CPU 按字访问,未对齐访问轻则慢(多次总线访问),重则硬件异常(某些平台)。
二、为什么需要对齐?
- 性能:现代 CPU 以「字(word,如 8 字节)」为单位从内存读数据。若 8 字节 double 跨越两个 8 字节 cache line/总线周期,需读两次拼接 → 慢。
- 硬件要求:部分架构(如部分 RISC/未对齐会 fault)强制对齐,否则崩溃。
- 原子性/可移植:对齐保证
std::atomic等在地址上有正确字边界。
三、对齐规则(C/C++)
结构体整体大小要满足:结构体大小是其「最大成员对齐值」的整数倍(并在成员间插入 padding 令每个成员都对齐)。
手算步骤:
- 每个成员的偏移须为自身对齐值的倍数,不足则补 padding。
- 结构体总大小须为最大成员对齐值的倍数,末尾补 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) == 12cpp
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 的原因。