110
56

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-08-20

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

110
56
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
110
56

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?