📜 目录
- 👋 背景
- 💻 Demo
- ✨ 坑点一:修改变量值导致通过地址引用该变量的地方也发生了修改
- ✨ 坑点二:直接修改内存值的操作比较危险
- 🧠 总结与经验
👋 背景
前几天发现了一个 bug,查了半天发现是一个指针类型赋值的问题,这里做一个 demo 演示一下,以后尽量规避下
💻 Demo
当时遇到的问题可以用下面这个场景概括下
王家两个兄弟,一个是王明,一个是王平
王明的英文名字是 ming wang
复制了王明创建了王平,并且修改王平的 FirstName 为 ping
期望王明的英文名字是 ming wang,王平的名字是 ping wang
但是结果他们的名字都是 ping wang,这不符合预期
package main
import "fmt"
type Name struct {
LastName string
FirstName *string
}
func (n Name) String() string {
return fmt.Sprintf("%s %s", *n.FirstName, n.LastName)
}
func main() {
first := "ming"
last := "wang"
wangming := Name{
LastName: last,
FirstName: &first,
}
wangping := wangming
first = "ping"
*wangping.FirstName = first
fmt.Println("wangming name is", wangming)
fmt.Println("wangping name is", wangping)
}
---
wangming name is ping wang
wangping name is ping wang
短短的代码,其实有两个问题,我们依次分析下
✨ 坑点一:修改变量值导致通过地址引用该变量的地方也发生了修改
让我们断点来看下,看下王明和王平是怎么了,怎么搞到一起了
可以看到,在 first = "ping"
将 first 的值修改成了 ping 时,此刻就影响了王明的 FirstName,因为王明的 FirstName 对应内存地址的就是 first 的内存地址
如果 first 发生了变化,王明的 FirstName 也就跟随发生了变化,从 ming wang
变成了 ping wang
既然上面是引用共用的 first 变量导致的问题,那我们新创建一个变量,不再使用 first 变量,应该可以解决这个问题
package main
import "fmt"
type Name struct {
LastName string
FirstName *string
}
func (n Name) String() string {
return fmt.Sprintf("%s %s", *n.FirstName, n.LastName)
}
func main() {
first := "ming"
last := "wang"
wangming := Name{
LastName: last,
FirstName: &first,
}
wangping := wangming
ping := "ping"
*wangping.FirstName = ping
fmt.Println("wangming name is", wangming)
fmt.Println("wangping name is", wangping)
}
---
wangming name is ping wang
wangping name is ping wang
很可惜,貌似并没有生效
✨ 坑点二:直接修改内存值的操作比较危险
我们再打断点看下问题出现在哪里
王平的 FirstName 和王明的 FirstName 内存地址竟然是一样的,不是明明使用了新的变量了吗
原因是 *wangping.FirstName = ping
这个操作,直接操作了内存值,修改了 FirstName 这个内存地址对应的值
wangming 的 FirstName 和 wangping 的 FirstName 的内存地址是一个,所以 *wangping.FirstName = ping
这个操作,就影响了这两位的 FirstName 了
那我们不修改内存地址的值,直接换个新内存地址的值嘞
package main
import "fmt"
type Name struct {
LastName string
FirstName *string
}
func (n Name) String() string {
return fmt.Sprintf("%s %s", *n.FirstName, n.LastName)
}
func main() {
first := "ming"
last := "wang"
wangming := Name{
LastName: last,
FirstName: &first,
}
wangping := wangming
ping := "ping"
wangping.FirstName = &ping
fmt.Println("wangming name is", wangming)
fmt.Println("wangping name is", wangping)
}
踩了两个坑之后,这次好使了
🧠 总结与经验
这是之前是在处理一个 k8s 的资源时遇到的问题,因为资源默认提供了 deepCopy 的方式,因此通过 deepCopy 的方式绕了过去解决了问题,这里不展开讲
后来在考虑往之前的版本 cherry-pick 这个修改的时候,通过单元测试发现,之前版本的代码没有这样的问题,所以才想起来这个问题除了 deepCopy 还有其他解决办法
总结来说,为了后面少处理类似的 bug,需要注意以下两点
- 结构体中尽量少的使用基本类型的指针值
- 处理这种基本类型指针值的时候,额外留意一下,尽量避免通过
*xx=xx
直接修改变量的值