From - 自由编程


先提一个问题:一下两段函数,外部调用的时候,返回值会发生值拷贝么?
代码1

1
2
3
4
5
func r1() (rr1 []mnode) {
rr1 = append(rr1, mnode{"h1", 1})
rr1 = append(rr1, mnode{"h2", 2})
return
}

代码2

1
2
3
4
5
func r2() (rr2 []*mnode) {
rr2 = append(rr2, &mnode{"h1", 1})
rr2 = append(rr2, &mnode{"h2", 2})
return
}

用过C语言的同学,可能对代码1多少有点儿疑问,首先这个返回值是分配在堆上还是栈上?返回的时候,会拷贝整个数组么?
那么我们来分析一下。
大家都知道,C语言中,临时变量分配在栈中,而通过“new”动作分配出来的变量,则分配在堆中。而Go不同,Go编译器会自动把有必要的临时变量分配在堆中,这个把临时变量分配在堆中的过程,叫做“内存逃逸”。
下面我们来分析一下函数r1的逃逸过程,首先看test.go文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
)

type mnode struct {
name string
id int
}

func r1() (rr1 []mnode) {
rr1 = append(rr1, mnode{"h1", 1})
rr1 = append(rr1, mnode{"h2", 2})
return
}

func main() {
mr1 := r1()
fmt.Println(mr1)
}

我们通过下面的命令行来进行整个程序的逃逸分析。

1
go build -gcflags '-m -l' test.go

输出

1
2
3
# command-line-arguments
./test.go:20:13: main ... argument does not escape
./test.go:20:13: mr1 escapes to heap

这里可以看出,变量mr1已经分配在堆上了
简单分析一下现在mr1和rr1内存分配情况,把rr1和mr1的地址打印出来看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
)

type mnode struct {
name string
id int
}

func r1() (rr1 []mnode) {
rr1 = append(rr1, mnode{"h1", 1})
rr1 = append(rr1, mnode{"h2", 2})
fmt.Printf("ptr-rr1: %p \n", &rr1)
fmt.Printf("ptr-rr1[1]: %p \n", &(rr1[1]))

return
}

func main() {
mr1 := r1()
fmt.Println("---------")
fmt.Printf("ptr-mr1: %p \n", &mr1)
fmt.Printf("ptr-mr1[1]: %p \n", &(mr1[1]))
}

然后执行 go run test.go,得到如下结果

1
2
3
4
5
6
$ go run test.go
ptr-rr1: 0xc00000c0a0
ptr-rr1[1]: 0xc000064198
---------
ptr-mr1: 0xc00000c080
ptr-mr1[1]: 0xc000064198

可以看到rr1和mr1的地址确实不同,但里面元素的地址