该传*t而非t当需修改原值或结构体过大(字段≥4–5个、含[]byte/map/slice等),否则小结构体传值更高效;指针非万能,误用反增逃逸与gc压力。

指针不是万能的内存优化开关,用错反而增加逃逸和 GC 压力;真正起效的关键是:控制拷贝、明确语义、配合逃逸分析。
什么时候该传 *T 而不是 T?
结构体字段超过 4–5 个,或含 []byte、map、slice、嵌套结构体时,值传参会触发完整拷贝——这不只是 CPU 开销,还可能迫使编译器把局部变量抬升到堆上。
- 推荐:
func process(u *User),尤其当User含Data []byte或Meta map[string]interface{} - 不推荐:
func process(u User),哪怕只调用一次,也复制全部字段 - 小结构体(如
type Point { X, Y int })传值更高效,解引用反而多一次内存访问 - 切片、map、chan 本身已是轻量级头部(24 字节),直接传值即可;写
func f(*[]int)是典型错误,既多余又易引发nilpanic
方法接收者该用指针还是值?
是否需要修改 receiver 是硬性门槛;但即使只读,大结构体也建议用指针接收者,否则每次调用都拷贝一份。
- 必须用指针:
func (u *User) Save()(要改u.LastLogin) - 强烈建议用指针:
func (u *User) ToJSON() []byte(User很大,避免拷贝) - 该用值:
func (p Point) Distance(q Point) float64(小结构体,无副作用) - 必须用指针:
func (m *sync.Mutex) Lock()(sync.Mutex不可拷贝) - 接口一致性:只要有一个方法用了指针接收者,整个类型最好统一用指针,否则
var u User; fmt.Printf("%v", u)可能因Stringer实现不一致而 panic
结构体字段要不要定义成指针(如 Name *string)?
除非你真需要区分“零值”和“未设置”,否则字段加星号是自找麻烦——序列化默认不输出 nil 字段,JSON 解析需显式加 omitempty,还容易在业务逻辑中漏判 nil。
- 适合用指针字段:API 请求参数(客户端可选填)、配置解析(
"timeout"字段缺失时应保持 unset 状态) - 不适合用指针字段:
ID *int64、Active *bool——用sql.NullInt64或自定义类型更安全 - 字段顺序影响内存对齐:把
[]byte、map等大字段放在结构体前面,能减少填充字节;指针字段本身只占 8 字节,但会破坏字段连续性,间接放大对齐开销
怎么验证指针有没有帮上忙?
别猜,用编译器说话。执行 go build -gcflags="-m -m" 看逃逸分析结果,重点关注:
-
... moves to heap:说明变量逃逸了,可能是你取了地址并返回,或传给了 goroutine -
... does not escape:恭喜,它大概率留在栈上 -
&u escapes to heap出现在return &u行,意味着每次调用都分配堆内存——这时该考虑sync.Pool复用,或直接返回值return u - 注意:不是所有
&都导致堆分配;编译器可能把小对象连同调用栈一起优化掉
最容易被忽略的是:指针本身不等于堆分配,而“让不该逃逸的东西逃逸”才是性能杀手。优化前先跑一遍 -m -m,否则很可能越改越慢。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











