Go 高手(11):结构体内存对齐——对齐规则、字段重排、size 优化
更新时间:2026-09-01。本文是
languages/go/intermediate/高手层第 11 篇,接 接口底层。结构体在内存中不是字段挨着字段放的,而是按对齐规则排列,字段之间可能有 padding(填充)。理解对齐规则,能帮你写出更紧凑、更高效的结构体。
本文要回答的问题
- 结构体的对齐规则是什么?为什么会有 padding?
- 字段重排能减少结构体大小吗?怎么排?
unsafe.Sizeof、unsafe.Offsetof、unsafe.Alignof分别做什么?- 对齐和 C 语言的 struct 对齐规则一样吗?
一、为什么需要对齐
CPU 读写内存时,按字大小读取(4 字节或 8 字节)。如果变量地址没有对齐到它的自然边界,CPU 可能需要多次内存访问才能读到完整数据。
int32要对齐到 4 字节边界(地址能被 4 整除)int64要对齐到 8 字节边界- 指针要对齐到 8 字节边界(64 位系统)
编译器自动插入 padding,保证每个字段都对齐到正确的边界。
二、对齐规则
Go 的对齐规则(64 位系统):
| 类型 | 对齐要求 | 大小 |
|---|---|---|
bool | 1 | 1 |
int8/uint8 | 1 | 1 |
int16/uint16 | 2 | 2 |
int32/uint32/float32 | 4 | 4 |
int64/uint64/float64 | 8 | 8 |
string | 8 | 16 |
slice | 8 | 24 |
map | 8 | 8 |
pointer | 8 | 8 |
结构体整体对齐规则:
- 结构体的大小是最大字段对齐值的倍数
- 每个字段的偏移量必须是该字段对齐值的倍数
三、字段重排优化
// 不好的排列
type Bad struct {
a bool // 1 byte,偏移 0
// padding 7 bytes(对齐到下一个 int64 边界)
b int64 // 8 bytes,偏移 8
c bool // 1 byte,偏移 16
// padding 7 bytes(结构体对齐到 max(8,8,1)=8 的倍数)
}
// 总大小:24 bytes,实际只用了 10 bytes,14 bytes 浪费
// 好的排列:从大到小排
type Good struct {
b int64 // 8 bytes,偏移 0
a bool // 1 byte,偏移 8
c bool // 1 byte,偏移 9
// padding 6 bytes(对齐到 8 的倍数)
}
// 总大小:16 bytes,只浪费 6 bytesBad struct(24 字节):
┌──┬──────┬──┬──────┬──┬──────┐
│a │pad │b │c │pad │
│1 │7 │8 │1 │7 │
└──┴──────┴─────────┴──┴──────┘
Good struct(16 字节):
┌─────────┬──┬──┬──────┐
│b │a │c │pad │
│8 │1 │1 │6 │
└─────────┴──┴──┴──────┘优化原则: 字段按对齐要求从大到小排列,减少 padding。
四、用 unsafe 查看对齐
import "unsafe"
type T struct {
a bool
b int64
c bool
}
t := T{}
fmt.Println(unsafe.Sizeof(t)) // 24
fmt.Println(unsafe.Alignof(t)) // 8
fmt.Println(unsafe.Offsetof(t.a)) // 0
fmt.Println(unsafe.Offsetof(t.b)) // 8
fmt.Println(unsafe.Offsetof(t.c)) // 16unsafe.Sizeof:返回结构体总共占用的字节数(包括 padding)unsafe.Alignof:返回结构体需要的对齐字节数unsafe.Offsetof:返回字段相对于结构体起始地址的偏移
五、空结构体 struct{} 的对齐
type Empty struct{}
fmt.Println(unsafe.Sizeof(Empty{})) // 0struct{} 的大小是 0。但作为字段时,它可能占用空间:
type T struct {
a int64
b struct{}
c bool
}
fmt.Println(unsafe.Sizeof(T{})) // 24如果 struct{} 在结构体末尾,Go 会在它前面加 padding,确保取 &t.b 不会越界。把 struct{} 放中间或开头,它不占空间。
六、对齐和 C 语言的对比
Go 的对齐规则和 C 语言基本一致(都是按照最大字段对齐),但 Go 不允许开发者控制对齐(C 可以用 #pragma pack)。Go 的规则更简单:自动对齐,开发者只能通过字段重排来优化。
七、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 字段乱排,结构体变大 | 结构体实际大小比预期大很多 | 按从大到小排列字段 |
| 大量结构体在内存中 | 每个浪费几个字节,百万个就浪费几 MB | 优化字段顺序 |
| struct{} 放末尾变大 | 结构体大小比预期大 | 把 struct{} 放开头或中间 |
| 假设字段连续排列 | 取字段地址发现不是连续的 | 用 unsafe.Offsetof 查看实际偏移 |
相关与延伸
下一篇:net/http 深入——Handler、中间件、ServeMux;C 语言的结构体对齐和
#pragma pack,见 C 结构体对齐。
一句话总结
Go 结构体内存对齐:字段按对齐要求排列,编译器自动插入 padding,保证每个字段地址对齐到自然边界;字段从大到小排列(int64 → int32 → bool)能减少 padding,优化结构体大小;unsafe.Sizeof/Offsetof/Alignof 查看实际内存布局;struct{} 大小 0,但放末尾可能产生 padding;Go 不能像 C 那样手动控制 pack,只能通过字段重排优化。