内存对齐
内存对齐
内存对齐是一种硬件约定与编译器优化策略:要求数据在内存中的起始地址满足某个特定的对齐边界(通常是数据类型大小或其倍数的整数倍)。
为什么需要内存对齐
硬件访问限制与效率
不同平台对内存访问有不同要求:
| 平台 | 对未对齐访问的处理方式 |
|---|---|
| x86(Intel) | 可以访问未对齐地址,但效率低 |
| ARM/MIPS/SPARC | 访问未对齐地址可能直接触发异常 |
举个例子(32 位 ARM):
int *p = (int *)0x1003; // 非对齐访问
int val = *p; // CPU 报错:Alignment fault
- 访问
0x1003开头的 4 字节int,CPU 要读0x1000~1003和0x1004~1007两个字。
为什么会慢?
- 对齐访问:只需一次 memory access(aligned word fetch)
- 非对齐访问:CPU 可能需要两次访问(split fetch),然后通过内部逻辑拼装结果
总线/缓存对齐
CPU 通常按「字」单位(4 或 8 字节)从内存中取数据;
如果数据没对齐,就可能跨越两个 cache line(典型为 64 字节),导致:
- 2 次 cache 访问(Cache Miss 增加)
- 内存带宽浪费
编译器对齐策略(C/C++ 为例)
对于结构体 struct S,编译器会遵循:
- 每个成员地址必须是其类型对齐数的整数倍
- 整个结构体大小是最大对齐数的整数倍
- 必要时插入 padding 字节
对齐数通常为类型大小(也可用 __alignof__ 获得),也就是说,int 占 4 个字节,它的地址一般要是 4 的倍数;double 占 8 个字节,它的地址一般要是 8 的倍数。
在某些平台或 ABI 规则下,对齐数 ≤ 类型大小,不会超过类型大小。例如:
- 在 32 位系统上,
double的大小是 8 字节,但可能只要求 4 字节对齐(节省空间)。 - 在结构体里,编译器会根据最大对齐数和填充规则来对齐成员。
示例:
struct S {
char a; // 1字节
int b; // 4字节
short c; // 2字节
};
在 32 位平台上(假设默认对齐):
| 成员 | 偏移 | 占用 | 说明 |
|---|---|---|---|
a |
0 | 1 | char 占1字节 |
pad1 |
1~3 | 3 | 填充,使 b 从地址 4 开始 |
b |
4~7 | 4 | int 对齐到 4 |
c |
8~9 | 2 | short 对齐到 2 |
pad2 |
10~11 | 2 | 结构体对齐到最大成员 4 字节的倍数 |
总大小:12 字节
强制修改对齐方式:
#pragma pack(1)
struct S { char a; int b; short c; };
#pragma pack()
- 强制结构体以 1 字节对齐,压缩空间,但性能可能下降(特别在非 x86 平台上)
对齐对性能的真实影响(汇编视角)
#include <stdio.h>
#include <stdint.h>
int main() {
// 定义一个长度为 8 的字节数组,内容从 1 到 8
// arr 的内存布局如下(每个数字代表一个字节):
// 地址: 0 1 2 3 4 5 6 7
// 内容: 1 2 3 4 5 6 7 8
uint8_t arr[8] = {1, 2, 3, 4, 5, 6, 7, 8};
// 对齐访问:从 arr[4] 开始,按 uint32_t 读取 4 字节(即读取 5,6,7,8)
// &arr[4] 是按 4 字节对齐的地址(假设 arr 本身是对齐的),所以是“对齐访问”
// val1 读取的是:
// 在小端机器上(x86/x64):val1 = 8 << 24 | 7 << 16 | 6 << 8 | 5
// => val1 = 0x08070605 = 134678021
// 在大端机器上: => val1 = 0x05060708 = 84281096
uint32_t *p_aligned = (uint32_t *)&arr[4];
uint32_t val1 = *p_aligned;
// 非对齐访问:从 arr[3] 开始,按 uint32_t 读取 4 字节(即读取 4,5,6,7)
// &arr[3] 是不是 4 的倍数(是 3),所以是“非对齐访问”
// 一些平台可能支持,但效率低;某些平台(如 ARM)甚至可能直接崩溃(SIGBUS)
uint32_t *p_unaligned = (uint32_t *)&arr[3];
uint32_t val2 = *p_unaligned;
// 打印两个值(按实际平台字节序决定结果)
// 在小端平台(如 x86),输出:
// val1 = 0x08070605 = 134678021
// val2 = 0x07060504 = 117835012
printf("%u %u\n", val1, val2);
return 0;
}
-
uint8_t是 1 字节类型,相当于unsigned char。 -
uint32_t是 4 字节类型(32 位无符号整数)。 -
小端序:低位字节在低地址 → 读取顺序为:最低字节在前。
-
对齐访问更快;非对齐访问在某些平台上性能差甚至出错。
-
uint32_t *强转后从非对齐地址读取是未定义行为(但 x86 通常会允许这么做)。
在 x86 平台上的行为(使用 GCC 编译 -O0 -m32)
编译器生成的汇编:
对齐访问(aligned):
mov eax, DWORD PTR [ebp-4]
DWORD PTR表示一次性取双字 (double word, 4 字节)数据[ebp-4]- 方括号表示“内存地址”。
ebp是 基址指针寄存器 (Base Pointer),在函数栈帧里用来定位局部变量和参数。ebp-4就是:栈帧基址往下偏移 4 个字节的位置。
- 地址是 4 的倍数,CPU 直接使用 一个指令 完成加载
非对齐访问(unaligned):
movzx eax, BYTE PTR [ebp-5] ; 读第1个字节
movzx ecx, BYTE PTR [ebp-4] ; 读第2个字节
shl ecx, 8
or eax, ecx
movzx ecx, BYTE PTR [ebp-3] ; 读第3个字节
shl ecx, 16
or eax, ecx
movzx ecx, BYTE PTR [ebp-2] ; 读第4个字节
shl ecx, 24
or eax, ecx
movzx把一个字节扩展成 32 位整数shl是左移操作(拼接 4 个字节组成 1 个整数)or是拼装的方式
对比
- 对齐访问:1 条指令
mov完成 - 非对齐访问:至少 4 条加载 + 3 条移位 + 3 条或运算
在 ARM、MIPS 平台(非 x86):
非对齐访问 会直接触发 SIGBUS 错误(对齐错误):
Bus error (core dumped)
除非编译时启用 -mno-unaligned-access(ARMv7 及更高)或者使用 memcpy 规避。
性能影响测试(以 GCC 为例)
对 aligned 与 unaligned 循环访问 1 亿次:
for (int i = 0; i < 100000000; i++) {
sum += *(uint32_t *)(arr + 4); // aligned
}
for (int i = 0; i < 100000000; i++) {
sum += *(uint32_t *)(arr + 3); // unaligned
}
- 对齐访问:约 50ms
- 非对齐访问:约 200ms+(慢了 3~5 倍)
如何规避非对齐访问?
- 避免强制类型转换指针造成非对齐访问
- 使用
memcpy()安全加载不对齐数据 - 结构体字段对齐或
#pragma pack后用memcpy装载大数据
对齐与数据结构设计的深远影响
未优化结构体:浪费空间
struct Bad {
char c1; // 1 byte
int i; // 4 bytes
char c2; // 1 byte
};
分析布局(在 32 位/64 位平台上)
| 成员 | 偏移 | 大小 | 说明 |
|---|---|---|---|
char c1 |
0 | 1 | |
pad1 |
1~3 | 3 | 填充到 4 字节对齐 |
int i |
4~7 | 4 | 已对齐 |
char c2 |
8 | 1 | |
pad2 |
9~11 | 3 | 填充到结构体整体对齐(4字节对齐) |
总大小:12 字节
优化结构体:减少 padding
struct Good {
int i; // 4 bytes
char c1; // 1 byte
char c2; // 1 byte
};
优化后布局
| 成员 | 偏移 | 大小 | 说明 |
|---|---|---|---|
int i |
0~3 | 4 | 已对齐 |
char c1 |
4 | 1 | |
char c2 |
5 | 1 | |
pad |
6~7 | 2 | 补到 4 字节对齐倍数 |
总大小:8 字节
对齐优化技巧:
| 技巧 | 原理 |
|---|---|
| 成员按从大到小排序 | 减少中间 padding 空隙 |
使用 #pragma pack |
强制压缩结构体,节省空间 |
| 拆成多个结构 | 热数据放一起,冷数据放另一结构(cache 优化) |

浙公网安备 33010602011771号