您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關(guān)使用Go struct的注意事項的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
其給出的例子一如下:
type People struct {} func main() { a := &People{} b := &People{} fmt.Println(a == b) }
你認為輸出結(jié)果是什么呢?
輸出結(jié)果是:false。
再稍加改造一下,例子二如下:
type People struct {} func main() { a := &People{} b := &People{} fmt.Printf("%p\n", a) fmt.Printf("%p\n", b) fmt.Println(a == b) }
輸出結(jié)果是:true。
他的問題是 "為什么第一個返回 false 第二個返回 true,是什么原因?qū)е碌?/strong>?
煎魚進一步的精簡這個例子,得到最小示例:
func main() { a := new(struct{}) b := new(struct{}) println(a, b, a == b) c := new(struct{}) d := new(struct{}) fmt.Println(c, d) println(c, d, c == d) }
輸出結(jié)果:
// a, b; a == b 0xc00005cf57 0xc00005cf57 false // c, d &{} &{} // c, d, c == d 0x118c370 0x118c370 true
第一段代碼的結(jié)果是 false,第二段的結(jié)果是 true,且可以看到內(nèi)存地址指向的完全一樣,也就是排除了輸出后變量內(nèi)存指向改變導致的原因。
進一步來看,似乎是 fmt.Print
方法導致的,但一個標準庫里的輸出方法,會導致這種奇怪的問題?
如果之前有被這個 “坑” 過,或有看過源碼的同學??赡苣軌蚩焖俚囊庾R到,導致這個輸出是逃逸分析所致的結(jié)果。
我們對例子進行逃逸分析:
// 源代碼結(jié)構(gòu) $ cat -n main.go 5 func main() { 6 a := new(struct{}) 7 b := new(struct{}) 8 println(a, b, a == b) 9 10 c := new(struct{}) 11 d := new(struct{}) 12 fmt.Println(c, d) 13 println(c, d, c == d) 14 } // 進行逃逸分析 $ go run -gcflags="-m -l" main.go # command-line-arguments ./main.go:6:10: a does not escape ./main.go:7:10: b does not escape ./main.go:10:10: c escapes to heap ./main.go:11:10: d escapes to heap ./main.go:12:13: ... argument does not escape
通過分析可得知變量 a, b 均是分配在棧中,而變量 c, d 分配在堆中。
其關(guān)鍵原因是因為調(diào)用了 fmt.Println
方法,該方法內(nèi)部是涉及到大量的反射相關(guān)方法的調(diào)用,會造成逃逸行為,也就是分配到堆上。
關(guān)注第一個細節(jié),就是 “為什么逃逸后,兩個空 struct 會是相等的?”。
這里主要與 Go runtime 的一個優(yōu)化細節(jié)有關(guān),如下:
// runtime/malloc.go var zerobase uintptr
變量 zerobase
是所有 0 字節(jié)分配的基礎(chǔ)地址。更進一步來講,就是空(0字節(jié))的在進行了逃逸分析后,往堆分配的都會指向 zerobase
這一個地址。
所以空 struct 在逃逸后本質(zhì)上指向了 zerobase
,其兩者比較就是相等的,返回了 true。
關(guān)注第二個細節(jié),就是 “為什么沒逃逸前,兩個空 struct 比較不相等?”。
從 Go spec 來看,這是 Go 團隊刻意而為之的設(shè)計,不希望大家依賴這一個來做判斷依據(jù)。如下:
This is an intentional language choice to give implementations flexibility in how they handle pointers to zero-sized objects. If every pointer to a zero-sized object were required to be different, then each allocation of a zero-sized object would have to allocate at least one byte. If every pointer to a zero-sized object were required to be the same, it would be different to handle taking the address of a zero-sized field within a larger struct.
還說了一句很經(jīng)典的,細品:
Pointers to distinct zero-size variables may or may not be equal.
另外空 struct 在實際使用中的場景是比較少的,常見的是:
設(shè)置 context,傳遞時作為 key 時用到。
設(shè)置空 struct 業(yè)務(wù)場景中臨時用到。
但業(yè)務(wù)場景的情況下,也大多數(shù)會隨著業(yè)務(wù)發(fā)展而不斷改變,假設(shè)有個遠古時代的 Go 代碼,依賴了空 struct 的直接判斷,豈不是事故上身?
因此 Go 團隊這番操作,與 Go map 的隨機性如出一轍,避免大家對這類邏輯的直接依賴,是值得思考的。
而在沒逃逸的場景下,兩個空 struct 的比較動作,你以為是真的在比較。實際上已經(jīng)在代碼優(yōu)化階段被直接優(yōu)化掉,轉(zhuǎn)為了 false。
因此,雖然在代碼上看上去是 == 在做比較,實際上結(jié)果是 a == b 時就直接轉(zhuǎn)為了 false,比都不需要比了。
你說妙不?
既然我們知道了他是在代碼優(yōu)化階段被優(yōu)化的,那么相對的,知道了原理的我們也可以借助在 go 編譯運行時的 gcflags 指令,讓他不優(yōu)化。
在運行前面的例子時,執(zhí)行 -gcflags="-N -l"
指令:
$ go run -gcflags="-N -l" main.go 0xc000092f06 0xc000092f06 true &{} &{} 0x118c370 0x118c370 true
你看,兩個比較的結(jié)果都是 true 了。
在今天這篇文章中,我們針對 Go 語言中的空結(jié)構(gòu)體(struct)的比較場景進行了進一步的補全。經(jīng)過這兩篇文章的洗禮,你會更好的理解 Go 結(jié)構(gòu)體為什么叫既可比較又不可比較了。
而空結(jié)構(gòu)比較的奇妙,主要原因如下:
若逃逸到堆上,空結(jié)構(gòu)體則默認分配的是 runtime.zerobase
變量,是專門用于分配到堆上的 0 字節(jié)基礎(chǔ)地址。因此兩個空結(jié)構(gòu)體,都是 runtime.zerobase
,一比較當然就是 true 了。
若沒有發(fā)生逃逸,也就分配到棧上。在 Go 編譯器的代碼優(yōu)化階段,會對其進行優(yōu)化,直接返回 false。并不是傳統(tǒng)意義上的,真的去比較了。
感謝各位的閱讀!關(guān)于“使用Go struct的注意事項”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。