Help us understand the problem. What is going on with this article?

golangではスタックとヒープを気にする必要が無い

More than 1 year has passed since last update.

調べようと思ったきっかけは、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++をやっていた人間からするとモヤモヤしますが。。。

rookx
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away