5

在许多语言中,局部变量位于调用堆栈中

在 JavaScript/Python 中,只有闭包变量位于堆中,因为它们必须存在于函数调用之外,它们是被创建的。


在 GO 中,一些 GO 类型(如 slice 类型[]int)确实引用了内存的其他部分,如 JavaScript/Python。

在 GO 中,并非所有类型的变量都包含引用,例如 Javascript/Python。

例如,

1)[3]int类型变量b直接存储 's 的数组int,如 C,除了 C 允许使用 C 语法访问每个数组元素位置&b[index],以获得更多控制

2)int类型变量c直接存储一个int值,就像 C 一样,不同的是,C 通过提供 syntax( &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在堆中分配?

4

1 回答 1

13

在 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分配在maingoroutine 的堆栈上,而变量b分配在堆上。从输出中我们可以看到,地址b在 0x1043xxxx 上,而所有其他地址在 0x1042xxxx 上。

$ ./1
[5/5]0x10432020
[5/5]0x10429f58
[5/5]0x10429f44
0x10429f40
于 2017-01-28T08:49:36.500 回答