2016-01-19 6 views
7

Rozważmy następujący program Go:Dlaczego przepełnienie stosu zależy od sposobu uzyskania dostępu do tablicy w systemie Go?

package main 

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

Z "go uruchomić test1.go", to działa. (. Chyba, że ​​masz za mało pamięci RAM)

Teraz rozszerzyć ten program trywialnie:

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 uruchomić test2.go" Wynik:

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 

Wydaje mi się, że w test1.go, użyto stosu, podczas gdy w test2.go stos był używany. Czemu?

Odpowiedz

9

Według Go specification:

Wyrażenie jego analizy, gdy przed rozpoczęciem pętli, z jednym wyjątkiem, że ekspresja zakres jest tablicą lub wskaźnik na tablicy i co najwyżej jednej zmiennej iteracji obecny, tylko długość zakresu wyrażenia jest oceniana

Tak więc w pierwszym programie tylko długość bufora jest oceniana i umieszczana na stosie.

W drugim programie cały bufor jest umieszczany na stosie przed jego iteracją. Ponieważ rozmiar bufora jest znany podczas kompilacji, kompilator Go umieszcza instrukcję na początku funkcji, aby wstępnie przydzielić przestrzeń stosu. Dlatego ślad paniki wskazuje na początek funkcji.

W obu przypadkach buffer jest przydzielany do sterty. To może być potwierdzona przez

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

zauważyć, że program będzie zachowywał się podobnie, jeśli uczynić buffer zmiennej globalnej.

Jeśli jednak zmienisz buffer na plaster (buffer := make([]float64, 100000000)), program zakończy się pomyślnie w obu przypadkach. Dzieje się tak, ponieważ plaster jest po prostu wskaźnikiem do rzeczywistej tablicy i zajmuje tylko kilka bajtów na stosie niezależnie od rozmiaru tablicy backing. Więc najprostszym sposobem, aby naprawić swój drugi program jest, aby iteracyjne nad plaster zamiast tablicy:

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

Niespodziewanie jeśli starają się stworzyć większy wachlarz [1000000000]float64 następnie kompilator będzie narzekać (ramka stosu zbyt duże (> 2GB)). Myślę, że powinien narzekać, gdy kompiluje się twój drugi program, zamiast pozwolić mu panikować. Możesz zgłosić ten problem do http://github.com/golang/go/issues

Powiązane problemy