Go指针危险点有三:nil解引用导致panic、循环变量地址复用使指针全指向同一值、并发裸共享引发数据竞态;需分别通过判空、堆分配、字段级锁或封装方法规避。

Go 里指针本身不危险,但踩中 nil 解引用、循环变量地址复用、并发裸共享这三处,基本就 panic 或数据错乱了。
解引用前不判 nil 就 panic
这是最直接的崩溃原因:对未初始化或显式赋为 nil 的指针做 * 操作,或调用其方法,立刻触发 panic: runtime error: invalid memory address or nil pointer dereference。
- 函数入参是
*User?第一行就得检查:if u == nil { return fmt.Errorf("user is nil") } - 结构体字段是
*string?访问前必须写:if u.MiddleName != nil { fmt.Println(*u.MiddleName) } - 从
map[string]interface{}取值后断言为*Config?不能跳步:if c, ok := data.(*Config); ok && c != nil { ... } - 接口变量存了
nil指针(如var u *User; i := interface{}(u)),i == nil是 false —— 正确判断需用reflect.ValueOf(i).IsNil(),但更推荐设计上避免这种赋值
for 循环里取局部变量地址导致所有指针指向同一值
循环中反复用同一个变量名创建结构体再取地址,Go 会复用栈空间,结果切片或 map 里所有指针都指向最后一次迭代的值。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 错误写法:
p := Person{Name: name}; people = append(people, &p)——&p始终指向同一栈地址 - 正确做法:每次迭代直接在堆上分配,写成
&Person{Name: name};或用临时变量但确保生命周期足够长(通常只有堆分配才安全) - 验证方式:打印每个元素地址:
fmt.Printf("%p\n", people[i]),若全一样就是踩坑了
map[string]*User 并发读写字段引发竞态
把 *User 存进 map,多个 goroutine 同时修改 users["a"].Name,即使 map 加了 sync.Mutex,也拦不住对底层结构体字段的竞态访问。
-
sync.Mutex只保护map的增删改查操作,不保护*User指向内容的读写 - 真正要保护的是结构体字段:要么在访问
users["a"]后,用另一个锁保护其字段;要么改用channel传递数据,而非共享指针 - 更稳妥的做法是避免在
map中存可变指针;若必须共享,优先封装成带锁的方法(如u.SetName()),把同步逻辑收口
返回局部变量地址看似合法实则语义易错
编译器会自动将“需要逃逸”的局部变量分配到堆上,所以 return &i 通常不会崩溃,但容易让人误以为“返回栈地址也没事”,从而写出语义错误的代码。
- 这不是运行时错误,而是隐性设计缺陷:调用方可能误以为返回的是独立副本,实际却共享底层内存
- 尤其在结构体嵌套指针字段时(如
Response *Response),未初始化就直接赋值字段(d.Response.response = out)就会 panic - 关键不是“能不能跑”,而是“是不是你真想要的语义”——建议显式用
new(Response)或&Response{}初始化,别依赖逃逸分析猜行为
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










