值类型变量默认在栈上分配,如int、小struct、[3]int等,内存连续且cpu缓存友好;含指针字段(如string、slice、map)的结构体会逃逸到堆。

值类型变量在栈上分配的典型布局
Go 的 int、struct(不含指针字段)、[3]int 等值类型默认在栈上分配,内存连续且无间接跳转。比如:
type Point struct { x, y int }
var p Point
此时 p 占用 16 字节(假设 64 位系统),p.x 和 p.y 紧邻存储,CPU 缓存友好。
- 结构体字段按声明顺序排列,但编译器会做填充对齐(如
byte后跟int64可能插入 7 字节 padding) - 小结构体(通常 ≤ 2 个机器字)传参时直接复制,避免逃逸分析开销
- 若结构体含
map、slice、string或指针字段,整个结构体可能逃逸到堆——不是因为它是“引用类型”,而是因为其内部字段需动态管理
什么时候必须用 & 取地址传指针
不是“为了性能”就该加指针,而是由语义和逃逸决定。以下情况 Go 编译器通常要求或强烈建议传指针:
- 方法接收者为指针(
func (p *Point) Move(dx, dy int)),否则无法修改原值 - 结构体过大(例如含大数组或多个大字段),复制开销明显,如
[1024]byte每次传参复制 1KB - 需要函数内修改调用方变量,如
json.Unmarshal([]byte, &v)中&v是必需的 - 变量本身已逃逸(如在闭包中捕获、被全局变量引用),此时取地址不增加额外开销,反而避免冗余拷贝
反例:对 int 或小结构体盲目加 *,不仅没收益,还可能干扰内联(编译器对指针参数更保守)。
立即学习“go语言免费学习笔记(深入)”;
new(T) 和 &T{} 的内存行为差异
两者都返回 *T,但初始化逻辑不同,影响逃逸和零值语义:
-
new(T)总是分配堆内存(逃逸),返回指向零值T{}的指针,字段全为零,不可指定初始值 -
&T{a: 1}在栈上构造再取地址;若编译器判定其不会逃逸,则全程栈操作;若逃逸(如返回该指针),才分配堆内存 - 对简单类型如
&int{42},Go 1.21+ 通常优化为栈分配 + 地址取值,比new(int)更轻量
错误认知:“new 更‘正规’”。实际上,显式字面量构造 + 取地址更可控,也更符合现代 Go 的逃逸优化策略。
指针字段导致结构体逃逸的隐蔽路径
一个看似纯值类型的结构体,可能因字段间接含指针而整体逃逸。例如:
type Config struct {
Name string // string 是 header 结构体,含指针字段
Data []byte // slice 同样含指针
}
即使你只写 var c Config,c 很可能逃逸到堆——因为 Name 和 Data 的底层数据必须动态分配。
- 用
go tool compile -gcflags="-m" main.go查看逃逸分析结果,重点关注 “moved to heap” 提示 - 若需栈驻留,考虑用固定长度数组替代
[]byte,或用unsafe.String(需谨慎)拼接常量字符串 - 嵌套结构体中,只要任意一层含指针字段(包括
interface{}、func()),整条链路都易触发逃逸
真正影响性能的往往不是“用了指针”,而是“本可栈驻留的变量被迫堆分配后引发 GC 压力和缓存失效”。看清逃逸原因,比盲目换指针或换值类型更重要。










