结构体内存布局
本教程共 80 篇 · 第 40 篇 · 更新于 2026-07-27 · 约 7 分钟阅读
40. 结构体内存布局
本节目标:搞懂结构体占多少内存、为什么要内存对齐、字段顺序怎么排能省空间,以及空结构体 struct{} 有什么用。
结构体占多大内存
结构体的大小等于所有字段大小之和吗?不一定。来看个例子:
package main
import (
"fmt"
"unsafe"
)
type Bad struct {
a bool // 1 字节
b int64 // 8 字节
c bool // 1 字节
}
type Good struct {
b int64 // 8 字节
a bool // 1 字节
c bool // 1 字节
}
func main() {
fmt.Println(unsafe.Sizeof(Bad{})) // 24
fmt.Println(unsafe.Sizeof(Good{})) // 16
}
同样的三个字段,换个顺序,大小从 24 变成 16。这就是内存对齐的影响。
Note
unsafe.Sizeof返回类型占用的字节数。这个函数名虽然带 unsafe,但只是读取大小,不做危险操作,可以放心用。
内存对齐是什么
CPU 读取内存不是逐字节的,而是按块(比如 8 字节)来读。如果数据正好放在块边界上,一次就能读完。如果跨了边界,得读两次再拼接,慢很多。
所以编译器会在字段之间插入「填充字节」(padding),让每个字段都对齐到自己大小的整数倍地址上。
以 Bad 为例:
字段 大小 对齐要求 实际位置
a 1 1 [0]
padding 7字节 [1-7]
b 8 8 [8-15]
c 1 1 [16]
padding 7字节 [17-23]
总计: 24 字节
Good 的情况:
字段 大小 对齐要求 实际位置
b 8 8 [0-7]
a 1 1 [8]
c 1 1 [9]
padding 6字节 [10-15]
总计: 16 字节
把大的字段放前面,小的放后面,减少了中间的 padding。
Tip排列字段的技巧:把大字段(int64、float64)放前面,小字段(bool、int8)放后面。这样能最大化减少 padding,省内存。
对齐规则
Go 的内存对齐规则:
- 每个字段的对齐值 = 该类型的大小(但最大不超过平台字长,64 位系统是 8)
- 结构体的对齐值 = 所有字段对齐值中最大的那个
- 结构体大小 = 对齐值的整数倍
常见类型的大小和对齐值(64 位系统):
| 类型 | 大小 | 对齐值 |
|---|---|---|
| bool | 1 | 1 |
| int8 / uint8 | 1 | 1 |
| int16 / uint16 | 2 | 2 |
| int32 / uint32 / float32 | 4 | 4 |
| int / int64 / uint64 / float64 | 8 | 8 |
| string | 16 | 8 |
| 指针 | 8 | 8 |
| 切片 | 24 | 8 |
字段排列实战
来看一个实际的优化案例:
// 优化前:浪费空间
type User1 struct {
Active bool // 1 + 7 padding
Created int64 // 8
Verified bool // 1 + 7 padding
Score int64 // 8
}
// 总大小: 32 字节
// 优化后:紧凑排列
type User2 struct {
Created int64 // 8
Score int64 // 8
Active bool // 1
Verified bool // 1 + 6 padding
}
// 总大小: 24 字节
省了 25% 的内存。在大量结构体(比如百万级的 slice)场景下,这个优化很可观。
Warning内存对齐优化通常不是首要任务。如果结构体数量不多,字段顺序怎么舒服怎么来。只有在大量实例的场景下才值得优化。
空结构体 struct{}
struct{} 是没有任何字段的结构体,大小为 0:
package main
import (
"fmt"
"unsafe"
)
func main() {
var s struct{}
fmt.Println(unsafe.Sizeof(s)) // 0
}
大小为 0 的类型有什么用?
用作信号通道
chan struct{} 是 Go 里常见的「信号通道」模式,表示「只关心有没有信号,不关心信号内容」:
done := make(chan struct{})
go func() {
// 干活...
close(done) // 完成后关闭通道
}()
<-done // 阻塞等待完成信号
用作集合
map[string]struct{} 可以当作集合(Set)来用,value 不占内存:
set := make(map[string]struct{})
set["apple"] = struct{}{}
set["banana"] = struct{}{}
if _, ok := set["apple"]; ok {
fmt.Println("apple 在集合里")
}
Tip用
struct{}做 map 的 value 比bool省 1 字节。虽然一个元素才省 1 字节,但百万级时差距明显。
小结
结构体内存布局的要点:
- 字段之间有 padding,大小不等于字段大小之和
- 大字段放前面,小字段放后面,能减少 padding
- 空结构体
struct{}大小为 0,适合做信号和集合 - 用
unsafe.Sizeof查看实际大小
下一部分进入接口,Go 类型系统的灵魂。