为什么堆栈溢出取决于Go中如何访问数组?

Why is the stack overflow depending on how to access the array in Go?

考虑以下 Go 程序:

package main

func main() {
    var buffer [100000000]float64
    var i int
    for i = range buffer {
        buffer[i] = float64(i)
    }
}

与 "go run test1.go" 一起使用。 (除非你的 RAM 太少。)

现在,我简单地扩展一下这个程序:

package main

func main() {
    var buffer [100000000]float64
    var i int
    var value float64
    for i, value = range buffer {
        value = value
        buffer[i] = float64(i)
    }
}

"go run test2.go" 产量:

runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

runtime stack:
runtime.throw(0x473350, 0xe)
        /usr/local/go/src/runtime/panic.go:527 +0x90
runtime.newstack()
        /usr/local/go/src/runtime/stack1.go:794 +0xb17
runtime.morestack()
        /usr/local/go/src/runtime/asm_amd64.s:330 +0x7f

goroutine 1 [stack growth]:
main.main()
        /home/bronger/src/go-test/test3.go:3 fp=0xc860075e50 sp=0xc860075e48
runtime.main()
        /usr/local/go/src/runtime/proc.go:111 +0x2b0 fp=0xc860075ea0 sp=0xc860075e50
runtime.goexit()
        /usr/local/go/src/runtime/asm_amd64.s:1696 +0x1 fp=0xc860075ea8 sp=0xc860075ea0
exit status 2

在我看来,在test1.go中使用了堆,而在test2.go中使用了栈。为什么?

根据 Go specification:

The range expression is evaluated once before beginning the loop, with one exception: if the range expression is an array or a pointer to an array and at most one iteration variable is present, only the range expression's length is evaluated

所以在第一个程序中,只计算缓冲区的长度并将其放入堆栈。

在第二个程序中,整个缓冲区在遍历之前被放置在堆栈上。因为缓冲区的大小在编译期间是已知的,所以 Go 编译器将指令放在函数开头的 pre-allocate 堆栈 space 中。这就是为什么 panic trace 指向函数开头的原因。

在这两种情况下,buffer 都分配在堆上。这可以通过

确认
$ go build -gcflags=-m
./main.go:4: moved to heap: buffer

请注意,如果您将 buffer 设为全局变量,程序的行为将类似。

但是,如果您将 buffer 更改为切片 (buffer := make([]float64, 100000000)),则程序在这两种情况下都会成功。发生这种情况是因为切片只是指向实际数组的指针,并且它只占用堆栈上的几个字节,与支持数组大小无关。因此,修复第二个程序的最简单方法是让它遍历切片而不是数组:

....
for i, value = range buffer[:] {
    ....
}

令人惊讶的是,如果您尝试创建更大的数组 [1000000000]float64,那么编译器会报错(堆栈帧太大 (>2GB))。我认为它在编译你的第二个程序时也应该抱怨而不是让它恐慌。您可能想将此问题报告给 http://github.com/golang/go/issues