Почему переполнение стека зависит от того, как получить доступ к массиву в Go?
рассмотрим следующую программу Go:
package main
func main() {
var buffer [100000000]float64
var i int
for i = range buffer {
buffer[i] = float64(i)
}
}
С "go run test1.Иди", она работает. (Если у вас нет слишком мало ОЗУ.)
Теперь, я расширяю эту программу тривиально:
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)
}
}
" запустите 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.иди, стопка была использована. Почему?
1 ответов
по словам спецификация пойти:
выражение диапазона оценивается один раз перед началом цикла, за одним исключением: если выражение диапазона является массивом или указателем на массив и присутствует не более одной переменной итерации, оценивается только длина выражения диапазона
таким образом, в первой программе оценивается только длина буфера и помещается в стек.
в рамках второй программы в целом буфер помещается в стек перед итерацией над ним. Поскольку размер буфера известен во время компиляции, компилятор Go помещает инструкцию в начало функции для предварительного выделения пространства стека. Вот почему 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