Tor*_*ger 7 stack-overflow arrays go
考虑以下Go程序:
package main
func main() {
var buffer [100000000]float64
var i int
for i = range buffer {
buffer[i] = float64(i)
}
}
Run Code Online (Sandbox Code Playgroud)
使用"go run test1.go",它可以工作.(除非你的内存太少.)
现在,我简单地扩展这个程序:
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)
}
}
Run Code Online (Sandbox Code Playgroud)
"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
Run Code Online (Sandbox Code Playgroud)
在我看来,在test1.go中,使用了堆,而在test2.go中,使用了堆栈.为什么?
根据Go规范:
范围表达式在开始循环之前计算一次,但有一个例外:如果范围表达式是数组或指向数组的指针,并且最多存在一个迭代变量,则仅评估范围表达式的长度
因此,在第一个程序中,只评估缓冲区的长度并将其放在堆栈上.
在第二个程序中,整个缓冲区被放置在堆栈上,然后迭代它.因为在编译期间已知缓冲区的大小,所以Go编译器将指令放在函数的开头以预分配堆栈空间.这就是为什么恐慌追踪指向函数的开头.
在这两种情况下buffer都在堆上分配.这可以通过确认
$ go build -gcflags=-m
./main.go:4: moved to heap: buffer
Run Code Online (Sandbox Code Playgroud)
请注意,如果创建buffer全局变量,程序的行为将类似.
但是,如果您更改buffer为slice(buffer := make([]float64, 100000000)),程序将在两种情况下都成功.发生这种情况是因为切片只是指向实际数组的指针,并且它在堆栈上只占用几个字节,与支持数组大小无关.因此,修复第二个程序的最简单方法是使其迭代切片而不是数组:
....
for i, value = range buffer[:] {
....
}
Run Code Online (Sandbox Code Playgroud)
令人惊讶的是,如果你试图创建一个更大的数组,[1000000000]float64那么编译器会抱怨(堆栈帧太大(> 2GB)).我认为在编译你的第二个程序时应该抱怨,而不是让它感到恐慌.您可能希望将此问题报告给http://github.com/golang/go/issues