惊!空 struct 地址竟然不相等
Go 語言里的空 struct{} 是一個特殊的結(jié)構(gòu),因為編譯器優(yōu)化的關系,會導致我們拿空 struct 指針做比較的時候出現(xiàn)一些意外的結(jié)果。之前有人提過相關的 issue,不過還是值得我們簡單研究一番。
官方 spec 中對此的描述是:"Pointers to distinct zero-size variables may or may not be equal.",來看看實現(xiàn)上具體為什么會是這樣的。
package mainimport "fmt"func main() {a := new(struct{})b := new(struct{})println(a, b, a == b)c := new(struct{})d := new(struct{})fmt.Println(c, d, c == d) }結(jié)果可能讓你很驚訝:
0xc00007af1f 0xc00007af1f false &{} &{} true如果之前碰到過給程序加上 fmt.Println 就導致結(jié)果變化的情況的同學,可能會想到這里的原因,是和逃逸分析相關。(可以參考這兩個 issue :8618 和 31317)
沒錯:
~/test git:master ??? cat -n true.go ? ?1 package main23 import "fmt"45 func main() {6 a := new(struct{})7 b := new(struct{})8 println(a, b, a == b)910 c := new(struct{})11 d := new(struct{})12 fmt.Println(c, d, c == d)13 } ~/test git:master ??? go run -gcflags="-m" true.go# command-line-arguments ./true.go:12:13: inlining call to fmt.Println ./true.go:6:10: main new(struct {}) does not escape ./true.go:7:10: main new(struct {}) does not escape ./true.go:10:10: new(struct {}) escapes to heap ./true.go:11:10: new(struct {}) escapes to heap ./true.go:12:13: c escapes to heap ./true.go:12:13: d escapes to heap ./true.go:12:22: c == d escapes to heap ./true.go:12:13: main []interface {} literal does not escape ./true.go:12:13: io.Writer(os.Stdout) escapes to heap <autogenerated>:1: (*File).close .this does not escape 0xc00007af1f 0xc00007af1f false &{} &{} true在 fmt.Println 中單獨出現(xiàn)了 c,所以 c = new 的時候就發(fā)生了逃逸,d 也同理:
未逃逸:a,b
逃逸:c,d
看過一點 go runtime 代碼的同學應該知道,在 runtime 里有這么個東西:
// base address for all 0-byte allocations var zerobase uintptr0 大小的內(nèi)存分配均會指向該變量地址,所謂的 0 字節(jié),主要就是這種空 struct。不過這里沒說清楚的是,只有在堆上分配的 0 大小的 struct 才會指向該地址:
package mainimport "fmt"var a = new(struct{}) // 堆上func main() {var b = new(struct{})fmt.Println(b/*這里單獨打印了,所以 b escape 到堆上*/, a == b) }那么空 struct 逃逸之后其實都是同一個變量,地址相等,我們是可以理解了。
沒逃逸的 a 和 b 為什么地址不相等呢?這里可以祭出 SSA 大法:
~/test git:master ??? sudo GOSSAFUNC=main go build true.go 可以看到這個 false,其實是在代碼優(yōu)化階段(opt)直接被編譯后端作為常量優(yōu)化掉了,直接轉(zhuǎn)成了 false。所以我們本能地以為 == 是在做指針比較,但是常量優(yōu)化會替我們做出不那么符合直覺的判斷,直接把 a == b rewrite 為 false。
問題到這里也就結(jié)束了,既然我們知道這里是優(yōu)化階段做的,那是不是我們把優(yōu)化關閉就會導致輸出 true 呢?
確實是的:
~/test git:master ??? cat true.gopackage mainimport "fmt"func main() {a := new(struct{})b := new(struct{})println(a, b, a == b)c := new(struct{})d := new(struct{})fmt.Println(c, d, c == d) }~/test git:master ??? go run -gcflags="-N -l" true.go0xc00007aece 0xc00007aece true &{} &{} true我們可以用 Go 附帶的 SSA 工具去更深入地認識整個編譯階段干的事情,從而理解官方的所謂“實現(xiàn)細節(jié)”和 spec 上的 may or may not 到底是怎么回事。
只要知道怎么使用工具,結(jié)論反而也就沒那么重要了。
[1]?https://github.com/golang/go/issues/8618
[2] https://github.com/golang/go/issues/31317
總結(jié)
以上是生活随笔為你收集整理的惊!空 struct 地址竟然不相等的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Go Modules 的智障版本选择
- 下一篇: 深度解密Go语言之sync.map