学习自曹大,实操一遍记录
package main import "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) }
0xc00007ff47 0xc00007ff47 false &{} &{} true
这个结果为何不一样,我看完的假设是内存逃逸导致的,但是为何会逃逸我也不清楚,这应该和编译器有关。看了曹大的解释猜想对了,但是曹大同时也做出了解释,但并不是规律性的解释,因为官方也是给的may or may not的解释~~
首先分析内存逃逸了
go run -gcflags="-m" main.go
# command-line-arguments .main.go:6:10: new(struct {}) does not escape .main.go:7:10: new(struct {}) does not escape .main.go:10:10: new(struct {}) escapes to heap .main.go:11:10: new(struct {}) escapes to heap .main.go:12:13: ... argument does not escape .main.go:12:22: c == d escapes to heap
很清晰了,但是为什么逃逸到堆上的就相等了
// base address for all 0-byte allocations var zerobase uintptr
// Allocate an object of size bytes. // Small objects are allocated from the per-P cache's free lists. // Large objects (> 32 kB) are allocated straight from the heap. func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") } if size == 0 { return unsafe.Pointer(&zerobase) } ... }
这个解释就清楚了,在mallocgc里做下调试就会发现空struct的size==0。
那么问题来了,为什么我们栈上就不可以这样,借助SSA
GOSSAFUNC=main go build main.go
这块涉及编译优化,不了解可以当个扫盲
把优化关了试试
go run -gcflags="-N -l" main.go
0xc0000c9e4e 0xc0000c9e4e true
&{} &{} true
其实对编译不了解也可以完全看懂,甚至可以学到一些小技巧,稍微总结一下
首先,内存逃逸应该要想到,然后基本的命令需要懂得
然后在看ssa的opt部分,即使你不懂编译,不懂源码也可以看到一些细节,比如没有逃逸的zerobase,我们就可以跟到源码看一下了,然后,一切都明白了。
补上四爷的著名文章之经典观点,哈哈。
https://mp.weixin.qq.com/s/PFAQOa1rYAl4YcSmGmEnQQ
https://github.com/golang/go/issues/8618
https://github.com/golang/go/issues/31317