该用指针传参当需修改原值或结构体含引用字段;否则小结构体(≤4字段且≤32字节)优先值传参以提升缓存局部性与避免解引用开销。

什么时候该用指针传参而不是值传参
Go 函数参数默认是值拷贝,结构体较大时直接传值会触发内存分配和复制,带来明显开销。是否用指针,关键看 struct 的大小和是否需要修改原值。
- 字段总数 ≤ 4 个基础类型(如
int、string、bool),且总大小 ≤ 32 字节,通常值传参更高效(避免解引用、缓存局部性更好) - 含切片、map、channel、interface 或指针字段的 struct,本身只存 header(24–32 字节),传值开销小,但语义上常需指针来修改底层数据
- 明确要修改调用方变量时,必须用
*T;否则即使性能稍差,也优先选值语义,减少隐式副作用
sync.Pool 里存指针还是值对象
存指针更常见,但不是因为“省复制”——而是避免逃逸和 GC 压力。值对象放入 sync.Pool 后,若被编译器判定为逃逸,会强制堆分配;而指针本身小,且能复用已分配对象。
- 推荐模式:
var pool = sync.Pool{New: func() interface{} { return &MyStruct{} }} - 取出后务必清空字段(尤其 slice、map),否则残留数据引发并发 bug
- 不要存带 finalizer 的指针,
sync.Pool不保证回收时机,finalizer 可能永远不执行
defer 中使用指针参数的坑
defer 捕获的是参数求值时刻的值,对指针来说,捕获的是地址,不是内容。如果 defer 前修改了指针指向的值,defer 执行时看到的就是新值。
func example() {
x := 10
p := &x
defer fmt.Println(*p) // 输出 20,不是 10
x = 20
}
- 想冻结当前值,得在 defer 前做一次显式拷贝:
v := *p; defer fmt.Println(v) - 对 map/slice 等引用类型,即使传值,defer 仍看到后续修改——本质仍是底层指针共享
- log、close、unlock 类操作,只要不依赖中间状态,通常无需额外处理
pprof 发现大量 allocs_from_ptr 是什么信号
这不是标准指标名,但如果你在 go tool pprof --alloc_space 中看到某函数下有高占比的 “allocs from pointer dereference”,大概率是循环中频繁解引用导致逃逸或冗余分配。
立即学习“go语言免费学习笔记(深入)”;
- 典型场景:在 for 循环里反复写
obj.field[i],而obj是指针且field是 slice,每次访问都可能触发 bounds check 分配临时 header - 优化方式:把
obj.field提前赋给局部变量,如fs := obj.field,再遍历fs[i] - 用
go build -gcflags="-m -m"确认变量是否逃逸;若本该栈分配却显示 “moved to heap”,就要检查指针传播链











