为什么堆栈溢出取决于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 中,使用了堆栈。为什么?


慕标5832272
浏览 262回答 1
1回答

摇曳的蔷薇

根据Go 规范:范围表达式在循环开始前计算一次,但有一个例外:如果范围表达式是数组或指向数组的指针,并且至多存在一个迭代变量,则仅计算范围表达式的长度因此,在第一个程序中,仅评估缓冲区的长度并将其放置在堆栈中。在第二个程序中,整个缓冲区在迭代之前被放置在堆栈上。因为在编译期间缓冲区的大小是已知的,所以 Go 编译器将指令放在函数的开头以预先分配堆栈空间。这就是为什么恐慌跟踪指向函数的开头。在这两种情况下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
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go