非得用T的唯一场景是让被调函数修改指针变量本身的指向;需传&ptr(即T)才能改变原指针值,否则仅改副本;常见于realloc模拟、CGO二级C指针传递等,但应优先考虑返回新指针等更安全替代方案。

什么时候非得用 **T(指向指针的指针)?
绝大多数 Go 场景根本不需要二级指针。你真正需要它的时刻,往往只出现在:要让被调函数修改「某个指针变量本身」的指向,而不是它指向的值。比如在函数里把一个 *int 从指向 a 改成指向 b,调用方必须传 &p(即 **int),否则改了也是白改。
- 常见错误现象:
func updatePtr(p *int) { p = &someOtherInt }—— 调用后原p指向完全没变 - 典型使用场景:实现类似 C 的
realloc行为、动态重绑定结构体字段指针、某些 unsafe 内存操作封装 - 参数差异:
*T传的是地址副本,**T传的是指针变量自身的地址,才能改那个变量
**string 和 *string 在赋值和解引用时怎么写才不 panic?
二级指针最容易在解引用时触发 nil panic,尤其当某一级是 nil 时。Go 不会自动“跳过”空指针——你必须显式检查每一层。
- 常见错误现象:
**pp = "new"在pp或*pp是nil时直接 panic - 安全写法:先检查
pp != nil && *pp != nil,再解引用;或者用if pp != nil { if s := *pp; s != nil { **pp = "new" } } - 初始化建议:不要写
var pp **string就完事,而是明确分配两级:s := "hello"; ps := &s; pps := &ps
用 **T 改结构体字段指针是否值得?
有时想通过函数动态替换结构体中某个 *Field 字段,比如缓存策略切换时换掉底层数据源指针。这时用 **Field 看似“优雅”,但代价常被低估。
- 性能影响:多一次内存寻址,且编译器更难做逃逸分析,容易导致额外堆分配
- 可读性陷阱:阅读代码时需来回确认哪一层是“谁的地址”,尤其嵌套在方法接收器里(如
func (t *Thing) SetData(pp **Data)) - 更稳妥替代:返回新指针 + 赋值给字段(
t.data = newDataPtr()),或用接口抽象行为而非硬换指针
CGO 或 unsafe 场景下 **C.char 怎么传才不出错?
对接 C 函数时,像 getenv 或某些回调要求传入 **char 接收输出,这里每级生命周期都得自己扛。
立即学习“go语言免费学习笔记(深入)”;
- 常见错误现象:
C.free(unsafe.Pointer(*cstr))崩溃,因为*cstr指向的内存不是C.malloc分配的 - 关键原则:C 侧写入的内存,必须由 C 侧释放;Go 侧申请的,用
C.free前确保它是C.malloc来的 - 实操建议:用
cstr := (**C.char)(C.malloc(C.size_t(unsafe.Sizeof((*C.char)(nil)))))分配二级指针空间,再传给 C 函数
多级指针真正的复杂点不在语法,而在所有权和生命周期的显式管理——Go 的简洁性恰恰藏在它不鼓励你轻易跨过这一层。一旦用了 **T,你就得同时盯住三个东西:指针变量本身、它指向的指针、以及最终指向的值。少盯住一个,运行时就可能给你个惊喜。










