一个Go函数返回值的拷贝问题

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的地址确实不同,但里面元素的地址

你可能感兴趣的:(一个Go函数返回值的拷贝问题)