調べようと思ったきっかけは、golangでは以下のように
ローカル変数のアドレスを戻り値としても問題ないということ。
package main
import (
"fmt"
)
type Animal struct {
Name string
Age int
}
func main() {
animal := allocAnimal()
fmt.Printf("allocate animal structure %p", animal)
}
func allocAnimal() *Animal {
return &Animal{}
}
ポインタを扱えるC/C++ではローカル変数のポインタを戻り値とした場合、スタック領域のポインタを関数外に渡すため、
コンパイル時点で警告が表示されます。(なぜエラーにしない)
実行時には最悪、セグメンテーションフォールトで落ちます。
そのため、mallocやnewでヒープ領域にメモリを割り当てる必要があります
なぜ、golangではこれが許されるのか気になり調べてみました
スタックとヒープについて
golangにおけるスタックとヒープについて簡単にですが、説明します。
スタック
- 確保・解放が早い
- 寿命が短い。関数内もしくは特定のスコープ内限定
- サイズが小さい
- ローカル変数・引数などで使われる
ヒープ
- 確保・解放が遅い
- 寿命は自由
- サイズが大きい
- newなどで確保される
golangのFAQ
golangのFAQにはスタックとヒープの扱いでは以下のように記載されています。
※全文はリンク先にて参照ください
[http://golang.jp/go_faq#stack_or_heap:title]
正確さを期するなら知る必要はありません。Goの各変数は参照されている限り存在し続けます。Goの実装によって選択された格納場所がどこかは、Go言語的には意味を持ちません。
と、どうやらコンパイラが適宜判断して、関数内に収まる場合はスタックに、関数外でも参照される変数はローカル変数でもヒープに割り当てられるようです。
実際に調べてみた
コンパイラにフラグを渡すと、メモリ割当の様子がわかります。
go build -gcflags -m hello.go
サンプルコードそのままの様子を見てみます。
$ go build -gcflags -m main.go
./main.go:17: can inline allocAnimal
./main.go:13: inlining call to allocAnimal
./main.go:14: animal escapes to heap
./main.go:13: &Animal literal escapes to heap
./main.go:14: main ... argument does not escape
./main.go:18: &Animal literal escapes to heap // ローカル変数がヒープに
コメントの箇所でコンパイラが関数の外で扱われると判断し、スタックではなくヒープに領域を確保しています。
newで確保したメモリを関数の外に出さない場合
では、コードを以下のようにするとどうでしょうか。
func allocAnimal() *Animal {
animal := new(Animal)
animal.Name = "Cat"
animal.Age = 23
return &Animal{}
}
このコードの内容にはあまり意味はありませんが、newしたものを関数内で完結させています。
このコードの様子を再度出力してみます。
./main.go:17: can inline allocAnimal
./main.go:13: inlining call to allocAnimal
./main.go:14: animal escapes to heap
./main.go:13: &Animal literal escapes to heap
./main.go:13: main new(Animal) does not escape // newしてもヒープに割り当てられない
./main.go:14: main ... argument does not escape
./main.go:22: &Animal literal escapes to heap
./main.go:18: allocAnimal new(Animal) does not escape
golangの場合はnewで確保しても関数内で完結するものはヒープではなくスタックに割り当てられるようです。
このように、golangでは特にスタックかヒープかというのを特に意識しなくても問題はないようです。
長く、C/C++をやっていた人間からするとモヤモヤしますが。。。