#前几天在论坛上看到一个这样的问题:
问题:分析一下,下面代码的输出是什么(判断a==c)的部分?
package main
import (
"fmt"
"runtime"
)
type obj struct{}
func main() {
a := &obj{}
fmt.Printf("%p\n", a)
c := &obj{}
fmt.Printf("%p\n", c)
fmt.Println(a == c)
}
很多人可能一看,a和c完全是2个不同的对象实例,便认为a和c具备不同的内存地址,故而判断a==c的结果为false。
但是我们看一下实际输出:
0x11a6c10
0x11a6c10
true
问题:为什么变量 a 和 c ,内存地址是一样的?
这里原文将逃逸和这个新建struct对象的问题混了,因为在新建对象时调用了 runtime 包的 newobject 函数。而 newobject 函数其实本质上会调用 runtime 包内的 mallocgc 函数。
这个函数有点特别:
如果要分配内存的变量不占用实际内存,则直接用 golang 的全局变量 zerobase 的地址。而我们的变量 a 和 变量 c 有一个共同特点,就是它们是“空 struct”,空 struct 是不占用内存空间的。
// 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")
}
// 关键部分,如果要分配内存的变量不占用实际内存,则直接用 golang 的全局变量 zerobase 的地址。
if size == 0 {
return unsafe.Pointer(&zerobase)
}
// ...
}
所以,a 和 c 是空 struct,在做内存分配的时候,使用了 golang 内部全局私有变量 zerobase 的内存地址。
所以导致了最后判断时地址是一样的。
同样如果我们放弃fmt包,通过println去打地址,也是有同一个问题
package main
import (
"fmt"
"runtime"
)
type obj struct{}
func main() {
a := &obj{}
// fmt.Println(a)
println(a)
b := &obj{}
println(b)
// fmt.Println(b)
println(a == b)
}
我们看结果
0xc000044767
0xc000044767
true