目标读者:
- 使用 C# / WPF 的 Windows 桌面工程师
- 需要调用 Win32 API / C / C++ DLL
- 会接触非托管内存、P/Invoke、共享内存、IPC
目标:
把「内存对齐」这个长期被模糊、被回避、被一句话带过的问题,
拆解到每一个字节、每一次 CPU 访问、每一次工程决策。
第一章:从“一个最小问题”开始
问题 1:内存到底是什么?
在程序世界里,内存不是变量,不是对象,不是字段。
内存只有三样东西:
1️⃣ 连续的地址
2️⃣ 每个地址 1 个字节
3️⃣ CPU 通过地址访问数据
地址: 0x1000 0x1001 0x1002 0x1003 ...
数据: 0x?? 0x?? 0x?? 0x??所有高级语言结构,最终都会被压平成字节序列。
问题 2:CPU 是怎么“读”内存的?
CPU 从来不是:
“我要 int,就读 4 个字节”
而是:
“我要 对齐地址上的一整块数据”
CPU 的访问单位包括:
- 总线宽度
- 寄存器宽度
- Cache Line
👉 这就是“对齐”存在的物理原因。
第二章:什么是内存对齐
2.1 对齐的严格定义
一个数据的起始地址,必须是其 对齐粒度(Alignment) 的整数倍。
| 类型 | 常见大小 | 常见对齐 |
|---|---|---|
| char / byte | 1 | 1 |
| short | 2 | 2 |
| int / float | 4 | 4 |
| double | 8 | 8 |
| pointer (x64) | 8 | 8 |
2.2 不对齐会发生什么(不是“慢一点”)
情况一:老 CPU
- 直接抛异常(Bus Error)
情况二:现代 CPU
- 拆成多次 load
- 合并数据
- 性能急剧下降
情况三:原子操作
- 未对齐 = 原子性破坏
👉 不对齐 = 潜在 Bug,不只是性能问题。
第三章:结构体内存布局的“铁三角规则”
规则 1:字段必须按自身对齐
struct A {
char c; // 1
int i; // 4
};内存布局:
[c][pad][pad][pad][i][i][i][i]sizeof(A) = 8
规则 2:结构体总大小必须是“最大对齐”的倍数
struct B {
int a; // 4
short b; // 2
};[a][a][a][a][b][b][pad][pad]规则 3:字段顺序决定 padding 数量
编译器不会帮你重排字段。
// 推荐
int a;
short b;
char c;
// 高风险
char c;
short b;
int a;第四章:为什么“要大 → 小”?(不是约定,是生存)
4.1 逐字段分析
char c; // 1
int i; // 4如果先放 char:
- int 必须跳到下一个 4 字节边界
- 产生 3 字节 padding
如果先放 int:
- char 紧跟即可
👉 不是美观,是避免跨界访问。
第五章:ABI —— 真正的“最终裁决者”
5.1 什么是 ABI
ABI(Application Binary Interface,应用程序二进制接口)规定:
- 数据布局
- 参数传递
- 返回值方式
- 对齐策略
👉 语言必须服从 ABI。
5.2 常见 ABI
- Win32 / Win64 ABI
- MSVC ABI
- System V ABI
不同 ABI:
- 对齐策略不同
- padding 不同
第六章:x86 vs x64 的隐藏杀伤力
| 项目 | x86 | x64 |
|---|---|---|
| 指针大小 | 4 | 8 |
| 对齐要求 | 相对宽松 | 严格 |
| 未对齐访问 | 多数可用 | 性能代价极高 |
👉 x64 是“放大镜”。
第七章:C# 结构体的真实行为
7.1 StructLayout
[StructLayout(LayoutKind.Sequential)]
struct S {}- Sequential:顺序
- Explicit:你自己负责
7.2 Pack 的真实含义
[StructLayout(LayoutKind.Sequential, Pack = 1)]Pack = 最大允许对齐粒度
❌ 不是“关闭对齐”
⚠️ Pack=1 = 高风险操作
第八章:Blittable 与跨语言生死线
8.1 什么是 Blittable
托管 / 非托管内存布局完全一致
8.2 安全类型
- int / float / double
- IntPtr
- fixed 数组
8.3 禁止类型
- string
- object
- class
- bool(极易炸)
第九章:结构体中“对象”的彻底解析
9.1 C# struct + class
struct S {
int A;
MyClass Obj;
}内存:
[A][Obj 的引用地址]👉 对齐的是指针,不是对象
9.2 C++ struct + 对象
struct S {
int A;
std::string Str;
};- 内部指针
- 构造 / 析构
- ABI 不稳定
❌ 禁止跨语言
第十章:bool / enum —— 对齐事故制造机
bool
| 来源 | 大小 |
|---|---|
| C# bool | 1 |
| C++ bool | 1(不稳定) |
| Win32 BOOL | 4 |
👉 永远不要假设 bool 对齐一致
enum
enum A : byte {}
enum B : int {}👉 enum = underlying type
第十一章:Cache Line 与 False Sharing
11.1 Cache Line
- 常见 64 bytes
- 一次读整行
11.2 False Sharing
- 不同字段
- 同一 cache line
- 多线程写
👉 性能雪崩
第十二章:共享内存 / IPC 的终极考验
12.1 共享内存的现实
- 没 GC
- 没 CLR
- 没语言
只有:
字节协议
12.2 工程级结构体模板
struct Header {
uint32_t Magic;
uint16_t Version;
uint16_t Size;
};- 固定头
- 明确版本
- 向后兼容
第十三章:开发过程中最常见的问题
- P/Invoke 参数错位
- x86 正常,x64 崩溃
- Marshal.SizeOf 不等于 sizeof
- 客户机器偶现 Bug
👉 90% 源于对齐错误
第十四章:检查清单
- 是否画过内存布局图?
- 是否明确 ABI?
- 是否避免 bool / string?
- 是否测试 x86 / x64?
第十五章:最终总结
内存对齐不是语言知识,而是系统工程能力。
当你能“看到”字节布局时,
你就已经拥有了对系统的控制权。