From - 自由编程
先提一个问题:一下两段函数,外部调用的时候,返回值会发生值拷贝么?
代码1
func r1() (rr1 []mnode) {
rr1 = append(rr1, mnode{"h1", 1})
rr1 = append(rr1, mnode{"h2", 2})
return
}
代码2
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文件内容如下:
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)
}
我们通过下面的命令行来进行整个程序的逃逸分析。
go build -gcflags '-m -l' test.go
输出
# 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的地址打印出来看一下
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,得到如下结果
$ go run test.go
ptr-rr1: 0xc00000c0a0
ptr-rr1[1]: 0xc000064198
---------
ptr-mr1: 0xc00000c080
ptr-mr1[1]: 0xc000064198
可以看到rr1和mr1的地址确实不同,但里面元素的地址