在 Go 中,你应该相信编译器会做出最好的决定。如果可能,它将在堆栈上分配内存。另请参阅常见问题解答:
从正确性的角度来看,您不需要知道。只要有对它的引用,Go 中的每个变量就存在。实现选择的存储位置与语言的语义无关。
存储位置确实对编写高效程序有影响。如果可能,Go 编译器将在该函数的堆栈帧中分配该函数的本地变量。但是,如果编译器在函数返回后无法证明该变量未被引用,那么编译器必须在垃圾回收堆上分配该变量以避免悬空指针错误。此外,如果局部变量非常大,将其存储在堆上而不是堆栈上可能更有意义。
在当前的编译器中,如果一个变量的地址被占用,那么该变量就是在堆上分配的候选者。但是,基本的逃逸分析会识别某些情况,即此类变量不会超过函数的返回值并且可以驻留在堆栈中。
如果没有优化(内联),yesa
将在堆中分配。我们可以通过-gcflags='-m'
(https://play.golang.org/p/l3cZFK5QHO)检查逃逸分析:
$ nl -ba 1.go
1 package main
2
3 func inlined() []int {
4 var a [5]int
5 return a[:]
6 }
7
8 //go:noinline
9 func no_inline() []int {
10 var b [5]int
11 return b[:]
12 }
13
14 func main() {
15 var local_array [5]int
16 var local_var int
17 println(no_inline())
18 println(inlined())
19 println(local_array[:])
20 println(&local_var)
21 }
$ go build -gcflags='-m' 1.go
# command-line-arguments
./1.go:3: can inline inlined
./1.go:18: inlining call to inlined
./1.go:5: a escapes to heap
./1.go:4: moved to heap: a
./1.go:11: b escapes to heap
./1.go:10: moved to heap: b
./1.go:18: main a does not escape
./1.go:19: main local_array does not escape
./1.go:20: main &local_var does not escape
我们看到编译器决定inlined.a
在第 5 行和no_inline.b
第 10 行在堆上分配,因为它们都超出了它们的范围。
但是,在内联之后,编译器注意到a
不再转义,因此它确定该变量可以再次分配到堆栈上(第 18 行)。
结果是变量a
分配在main
goroutine 的堆栈上,而变量b
分配在堆上。从输出中我们可以看到,地址b
在 0x1043xxxx 上,而所有其他地址在 0x1042xxxx 上。
$ ./1
[5/5]0x10432020
[5/5]0x10429f58
[5/5]0x10429f44
0x10429f40