解引用指针前必须检查是否为nil,否则必然panic;unsafe和reflect解引用风险更高,需严格校验;结构体字段访问隐式解引用仍需nil检查;小结构体优先值传递。

什么时候不该用 *p 解引用
直接对指针解引用最常见风险是 panic:如果 p 是 nil,*p 会触发 panic: runtime Error: invalid memory address or nil pointer dereference。这不是“偶尔出错”,而是只要路径走到那里就必然崩溃。
典型误用场景包括:函数参数声明为 *T 但未校验是否为 nil;从 map、channel 或接口断言中取出指针后跳过非空检查;在 defer 中无条件解引用(比如 defer func() { log.Println(*err) }(),而 err 可能为 nil)。
- 所有解引用前,先判断
p != nil—— 不要依赖“它肯定不为空”的假设 - 若逻辑上允许
nil,就用值接收或显式返回错误,而非强制解引用 - 避免在 defer、recover 或日志打印中隐式解引用;改用
fmt.Sprintf("%v", p)这类安全方式输出指针内容
unsafe.pointer 和 reflect 解引用的额外风险
这类操作绕过 go 类型系统和 GC 保护,一旦出错不是 panic,而是内存越界、数据损坏或静默错误。例如用 unsafe.Pointer 强转 *int 到 *String 后解引用,结果不可预测;或用 reflect.Value.Elem() 对非地址类型调用,会 panic。
-
unsafe.Pointer解引用前必须确保目标内存有效且生命周期足够长(不能指向栈上已退出作用域的变量) -
reflect.Value.Elem()前必须用CanAddr()和kind() == reflect.Ptr双重校验 - 除非对接 C、写底层库或性能极端敏感(如字节解析),否则一律避免
unsafe解引用
结构体字段访问时的隐式解引用陷阱
Go 允许对结构体指针直接访问字段(如 p.Name),这看起来像“自动解引用”,但容易掩盖 p 本身为 nil 的问题——此时仍会 panic,只是错误位置更隐蔽(发生在字段访问处,而非显式 *p 处)。
立即学习“go语言免费学习笔记(深入)”;
- 这种语法糖 ≠ 安全访问;
p.Name等价于(*p).Name,同样要求p != nil - 若字段是嵌套指针(如
p.Config.Timeout),需逐层确认p != nil、p.Config != nil - 考虑用方法封装访问逻辑,例如
func (p *Config) GetTimeout() time.Duration { if p == nil { return defaultTimeout } return p.Timeout }
用值传递替代指针解引用的适用边界
小结构体(如 type Point Struct{ X, Y int })按值传递开销极小,且天然规避解引用风险。盲目传指针反而增加 GC 压力(逃逸分析可能让本该栈分配的变量堆分配)。
- 结构体大小 ≤ 机器字长(通常 8 字节)时,优先值传递
- 若只读访问且结构体含指针或大 slice,传指针更合理;但需同步承担
nil检查责任 - 用
go tool compile -gcflags="-m"观察变量是否逃逸——若值传递没导致逃逸,就别加星号
实际写代码时,最常被忽略的是:**解引用不是“取值动作”,而是“信任动作”——你是在向编译器和运行时担保那块内存存在且可用。这个担保比写几行逻辑重得多。**