#前几天在论坛上看到一个这样的问题:

问题:分析一下,下面代码的输出是什么(判断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