Go函数传参只有值传递,T传的是地址副本;修改p影响原值,但p重指向不影响外部指针;判断传*T还是T需综合考虑是否需修改原值、结构体大小及是否含不可拷贝字段。

Go 函数传参时指针到底传了什么
Go 里没有“引用传递”,只有值传递。传 *T 类型参数时,传递的是指针变量的副本——也就是一个内存地址的拷贝。这个副本和原指针指向同一块堆/栈内存,所以修改 *p 能影响原值;但若在函数内让 p 指向别处(比如 p = &x),对外部原指针无任何影响。
常见误判场景:
- 以为
func foo(p *int) { p = new(int) }能改变调用方的指针变量本身 → 实际不能 - 对小结构体盲目加
*传参,反而增加一次内存寻址开销 - 在 defer 中解引用已释放的指针(如循环中取切片元素地址后延迟打印)→ 可能 panic 或读到脏数据
什么时候该传 *T,什么时候该传 T
判断核心不是“要不要改内容”,而是“改不改得动”+“值有多大”:
- 需要修改调用方变量所指向的值 → 必须传
*T,例如json.Unmarshal第二个参数是interface{},但实际常传&v -
T是大结构体(比如超过 24 字节)→ 传*T避免复制开销;但要注意:如果函数只读、且编译器能逃逸分析出栈分配,传值可能更快 -
T是sync.Mutex、http.Client等含不可拷贝字段的类型 → 只能传*T,否则编译报错cannot be copied - 返回值是
*T但后续只读 → 考虑是否真需暴露指针,避免调用方意外修改内部状态
*T 解引用时 panic 的典型原因
最常见的是 nil 指针解引用:panic: runtime error: invalid memory address or nil pointer dereference。它不一定发生在 *p 这一刻,而可能在调用 p.Method() 时触发(方法集隐式解引用)。
立即学习“go语言免费学习笔记(深入)”;
高频出错点:
- map 中存
*T,但 key 不存在时直接val := m[key]; *val→val是零值nil - 接口变量底层是
*T,但未做非空检查就断言并解引用:if v, ok := i.(*MyStruct); ok { use(*v) }→ 若i是nil接口,v是nil指针,*vpanic - 使用
new(T)后忘记初始化字段,直接访问嵌套指针字段:p = new(Conf); p.DB.Addr→p.DB是 nil
逃逸分析对指针行为的实际影响
Go 编译器会决定变量分配在栈还是堆,这直接影响指针的有效性。用 go build -gcflags="-m" 可查看:
例如:
func getPtr() *int {
x := 42
return &x // x 逃逸到堆,否则返回栈地址非法
}
但以下不会逃逸:
func sum(a, b int) int {
p := &a
return *p + b // p 在栈上,&a 不逃逸,因为没被返回或存储到全局
关键提醒:
- 不要依赖“局部变量一定在栈上”来写指针逻辑,逃逸规则会随 Go 版本微调
- benchmark 中发现指针版本更慢?先看是否因逃逸导致堆分配增多、GC 压力上升
- 结构体字段含指针(如
type S struct{ data *[]byte })易引发意外逃逸,应尽量扁平化或用 slice 替代










