@[toc]
引入
觀察如下代碼
func main() {
a := new(struct{})
b := new(struct{})
println("println result: ", a, b, a == b)
c := new(struct{})
d := new(struct{})
fmt.Printf("fmt.Printf result: %p\t%p\t%t\n", c, d, c == d)
}
輸出結(jié)果是
fmt.Printf result: 0xbedde0 0xbedde0 true
println result: 0xc00011df47 0xc00011df47 false
那么問(wèn)題來(lái)了,為什么第一個(gè)返回false彭则,第二個(gè)返回true,并且順序也不一致
打印順序
控制臺(tái)可以看到兩個(gè)打印顏色是不同的println的是紅色,fmt.Printf是白色
因?yàn)閜rintln打印輸出到os.Stderr
fmt.Printf打印輸出到os.Stdout
println是Go在實(shí)現(xiàn)自舉的時(shí)候供開(kāi)發(fā)人員打印使用的阎肝,后續(xù)并不能保證其能正常工作
結(jié)果分析
看過(guò)fmt源碼的話甩骏,很快意識(shí)到窗市,可能是逃逸分析導(dǎo)致先慷,我們對(duì)例子進(jìn)行逃逸分析
go run -gcflags="-m -l" main.go
# command-line-arguments
.\main.go:14:10: new(struct {}) does not escape
.\main.go:15:10: new(struct {}) does not escape
.\main.go:18:10: new(struct {}) escapes to heap
.\main.go:19:10: new(struct {}) escapes to heap
.\main.go:20:12: ... argument does not escape
.\main.go:20:56: c == d escapes to heap
println result: 0xc00011df47 0xc00011df47 false
fmt.Printf result: 0x9edde0 0x9edde0 true
通過(guò)分析可得知變量a,b分配在棧中咨察,c论熙,d分配在堆中。
關(guān)鍵原因是fmt的Print方法內(nèi)部涉及大量的反射相關(guān)方法的調(diào)用摄狱,會(huì)造成逃逸行為脓诡,也就是分配到堆上。
為什么逃逸后相等
這里主要和Go runtime的一個(gè)優(yōu)化細(xì)節(jié)有關(guān)
// runtime/malloc.go
var zerobase uintptr
變量 zerobase
是所有 0 字節(jié)分配的基礎(chǔ)地址媒役。更進(jìn)一步來(lái)講祝谚,就是空(0字節(jié))的在進(jìn)行了逃逸分析后,往堆分配的都會(huì)指向 zerobase
這一個(gè)地址刊愚。
所以空 struct 在逃逸后本質(zhì)上指向了 zerobase
踊跟,其兩者比較就是相等的,返回了 true鸥诽。
為什么不逃逸不相等
這是Go團(tuán)隊(duì)故意設(shè)計(jì)的商玫,不希望大家依賴這個(gè)來(lái)做判斷依據(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.
Pointers to distinct zero-size variables may or may not be equal.
在沒(méi)逃逸的場(chǎng)景下,兩個(gè)空 struct 的比較動(dòng)作牡借,并不是真的在比較拳昌。實(shí)際上已經(jīng)在代碼優(yōu)化階段被直接優(yōu)化掉,轉(zhuǎn)為了 false钠龙。
因此炬藤,雖然在代碼上看上去是 == 在做比較,實(shí)際上結(jié)果是 a == b 時(shí)就直接轉(zhuǎn)為了 false碴里,比都不需要比了沈矿。
總結(jié)
- 若逃逸到堆上,空結(jié)構(gòu)體則默認(rèn)分配的是 runtime.zerobase 變量咬腋,是專門用于分配到堆上的 0 字節(jié)基礎(chǔ)地址羹膳。因此兩個(gè)空結(jié)構(gòu)體,都是 runtime.zerobase根竿,一比較當(dāng)然就是 true 了陵像。
- 若沒(méi)有發(fā)生逃逸,也就分配到棧上寇壳。在 Go 編譯器的代碼優(yōu)化階段醒颖,會(huì)對(duì)其進(jìn)行優(yōu)化,直接返回 false壳炎。并不是傳統(tǒng)意義上的泞歉,真的去比較了。