传结构体指针更省内存,因值传递会完整复制整个结构体,而指针仅传8字节;含sync.Mutex、需修改原字段、超128字节或接口实现要求时必须用指针。
为什么传结构体指针比传值更省内存
Go 中结构体是值类型,func f(s MyStruct) 会完整复制整个结构体到栈上。如果结构体含大量字段、切片或嵌套大对象(比如含 []byte 或 map[string]interface{}),一次调用就可能拷贝几百字节甚至几KB——不仅浪费内存,还拖慢函数调用速度。而传指针 *MyStruct 始终只传 8 字节(64 位系统),复制开销恒定。
哪些结构体必须用指针传递
以下情况不传指针会出错或低效:
- 结构体字段含
sync.Mutex或其他不可复制类型(编译报错:cannot assign to struct containing sync.Mutex) - 需要在函数内修改原结构体字段(传值只能改副本)
- 结构体大小超过 128 字节(Go 编译器对大值类型自动优化为隐式指针传递,但行为不透明,显式用指针更可靠)
- 作为接口实现时,方法集不一致:只有
*T实现了某接口,你却传T值,会导致cannot use t (type T) as type Interface
如何安全地传结构体指针并避免 panic
传指针本身不危险,但解引用前未判空会 panic。常见错误是忽略零值指针:
func processUser(u *User) {
fmt.Println(u.Name) // 如果 u == nil,这里 panic: invalid memory address
}
正确做法:
- 函数内部加
if u == nil检查,并明确返回错误或 panic 带提示 - 构造函数(如
NewUser())统一返回*User,避免裸写&User{}后忘记检查 - 若结构体字段含指针(如
Profile *Profile),注意深层字段是否为 n
il,不要无脑链式访问 u.Profile.AvatarURL - 切片和 map 字段本身是指针包装,即使结构体传值,它们底层数据也不会被复制——但这不等于整个结构体“小”,仍需按总大小判断是否该传指针
性能对比:实测 1KB 结构体的调用开销
用 benchstat 对比两种方式(结构体含 1000 个 int64 字段,约 8KB):
func BenchmarkStructByValue(b *testing.B) {
s := makeLargeStruct()
for i := 0; i < b.N; i++ {
consumeByValue(s)
}
}
func BenchmarkStructByPtr(b *testing.B) {
s := makeLargeStruct()
for i := 0; i < b.N; i++ {
consumeByPtr(&s)
}
}
结果典型差异:
-
BenchmarkStructByValue-8:~350 ns/op,分配 ~8KB 内存/次 -
BenchmarkStructByPtr-8:~2 ns/op,分配 0 B
真正容易被忽略的是:哪怕结构体只有几个字段,只要它被高频调用(如 HTTP handler 中每请求一次),累积的栈复制和 GC 压力也会明显上升。别只盯着单次“看起来不大”。

il,不要无脑链式访问 






