Почему переполнение стека зависит от того, как получить доступ к массиву в 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