?? 目錄
- ?? 背景
- ?? Demo
- ? 坑點一:修改變量值導致通過地址引用該變量的地方也發(fā)生了修改
- ? 坑點二:直接修改內存值的操作比較危險
- ?? 總結與經驗
?? 背景
前幾天發(fā)現(xiàn)了一個 bug慷荔,查了半天發(fā)現(xiàn)是一個指針類型賦值的問題,這里做一個 demo 演示一下,以后盡量規(guī)避下
?? Demo
當時遇到的問題可以用下面這個場景概括下
王家兩個兄弟褥紫,一個是王明玄货,一個是王平
王明的英文名字是 ming wang
復制了王明創(chuàng)建了王平材彪,并且修改王平的 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
短短的代碼则拷,其實有兩個問題贡蓖,我們依次分析下
? 坑點一:修改變量值導致通過地址引用該變量的地方也發(fā)生了修改
讓我們斷點來看下,看下王明和王平是怎么了煌茬,怎么搞到一起了
可以看到斥铺,在 first = "ping"
將 first 的值修改成了 ping 時,此刻就影響了王明的 FirstName坛善,因為王明的 FirstName 對應內存地址的就是 first 的內存地址
如果 first 發(fā)生了變化晾蜘,王明的 FirstName 也就跟隨發(fā)生了變化,從 ming wang
變成了 ping wang
既然上面是引用共用的 first 變量導致的問題眠屎,那我們新創(chuàng)建一個變量剔交,不再使用 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
很可惜改衩,貌似并沒有生效
? 坑點二:直接修改內存值的操作比較危險
我們再打斷點看下問題出現(xiàn)在哪里
王平的 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 這個修改的時候,通過單元測試發(fā)現(xiàn),之前版本的代碼沒有這樣的問題惋戏,所以才想起來這個問題除了 deepCopy 還有其他解決辦法
總結來說领追,為了后面少處理類似的 bug,需要注意以下兩點
- 結構體中盡量少的使用基本類型的指針值
- 處理這種基本類型指針值的時候响逢,額外留意一下绒窑,盡量避免通過
*xx=xx
直接修改變量的值