在许多语言中,局部变量位于调用堆栈
中在JavaScript / Python中,只有闭包变量位于堆中,因为它们必须存在于函数调用之外,它们才会被创建。
在GO中,一些GO类型(如切片类型[]int
)会引用内存的其他部分,如JavaScript / Python。
在GO中,并非所有类型的变量都包含引用,例如Javascript / Python。
例如,
1)[3]int
类型变量b
直接存储int
的数组,如C,但C允许使用C访问每个数组元素位置语法&b[index]
,用于更多控制
2)int
类型变量c
直接存储int
值,如C,除了C,通过提供语法(&c
)来获得更多控制权来获取位置访问。
在GO中,我的理解是,局部变量位于堆/堆栈上取决于在示例代码中应用编译器的转义分析(下面),
func foo() []int {
// the array lives beyond the call to foo in which it is created
var a [5]int
return a[:] // range operator
}
告诉编译器变量a
超出其范围,因此在堆中分配,但不是堆栈。
问题:
变量a
是否在堆中分配?
答案 0 :(得分:7)
在Go中,您应该信任编译器以做出最佳决策。如果可能的话,它将在堆栈上分配内存。另请参阅the FAQ:
从正确的角度来看,你不需要知道。 Go中的每个变量都存在,只要有对它的引用即可。实现选择的存储位置与语言的语义无关。
存储位置确实会影响编写高效的程序。如果可能,Go编译器将在该函数的堆栈帧中分配函数本地的变量。但是,如果编译器在函数返回后无法证明变量未被引用,则编译器必须在垃圾收集堆上分配变量以避免悬空指针错误。此外,如果局部变量非常大,将它存储在堆而不是堆栈上可能更有意义。
在当前的编译器中,如果变量具有其地址,则该变量是堆上分配的候选变量。但是,基本的转义分析可以识别某些情况,这些变量不会超过函数的返回值并且可以驻留在堆栈上。
如果没有优化(内联),是a
将在堆中分配。我们可以通过传递-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
我们看到编译器决定在第5行分配inlined.a
而在第10行分配no_inline.b
,因为它们都逃避了它们的范围。
但是,在内联之后,编译器注意到a
不再转义,因此它确定可以再次在堆栈上分配变量(第18行)。
结果是变量a
在main
goroutine堆栈上分配,而变量b
在堆上分配。正如我们从输出中看到的那样,b
的地址在0x1043xxxx上,而其他所有在0x1042xxxx上。
$ ./1
[5/5]0x10432020
[5/5]0x10429f58
[5/5]0x10429f44
0x10429f40