Menu

Go言語のポインタ:&と*、new、nil、使いどころ

ポインタは値のアドレスを保持します。&と*、new、構造体へのポインタ、ローカル変数へのポインタを安全に返せる理由、ポインタを使うべき場面、そしてnilポインタの参照外しによるpanicを解説します。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

&と*

ポインタは値のメモリアドレスを保持します。&xx へのポインタを返します。*p はポインタをたどって値にたどり着きます。

アスタリスクには2つの役割があります。型の中(*int)では「intへのポインタ」を意味します。式の中(*p)では「pが指す値」を意味します。p そのものを表示すると 0xc000012345 のようなアドレスが表示され、実行するたびに変わります。

ポインタがある理由:コピーではなく共有

Goはすべてを値で渡します。関数は各引数のコピーを受け取るので、呼び出し側の変数を変更できません。ポインタを渡すと代わりにアドレスがコピーされ、両方が同じ値にたどり着きます。

GoにC++の意味での参照はありません。Goでの「参照渡し」とは、ポインタを値で渡すことです。

構造体へのポインタ

Goのコードで最もよく使われるポインタは構造体へのポインタです。フィールドへのアクセスは自動で参照外しされます。p.Name(*p).Name という意味です。

メソッドも同じ理屈に従います。ポインタレシーバーを使うと、メソッドが構造体を変更できます。値レシーバーとポインタレシーバーの選び方はメソッドのページで扱っています。

new

new(T) はゼロ値の T を確保し、そのアドレスを返します。変数を宣言してそのアドレスを取るのと同じです。

p := new(int) // *int pointing at 0
var x int
q := &x           // same thing, two lines
a := new(Account) // same as &Account{}

実際には、構造体には &T{...} のほうがよく使われます。同じ式でフィールドを設定できるからです。new は基本型へのポインタに便利です。スライス、マップ、チャネルを作ってポインタではなくすぐ使える状態で返す make と混同しないでください。

ローカル変数へのポインタを返すのは安全

Cでは、ローカル変数のアドレスを返すのはバグです。Goではごく普通のコードです。

値がスタックとヒープのどちらに置かれるかは、コンパイラのエスケープ解析が決めます。自分で選ぶことはなく、メモリを解放することもありません。何も指さなくなったら、ガベージコレクタが回収します。気になるなら go build -gcflags=-m でその判断を表示できます。

nilポインタ

どのポインタ型もゼロ値は nil です。nilを参照外しするとpanicします。

出力:

1
2
true
recovered: runtime error: invalid memory address or nil pointer dereference

recover がなければ、プログラムはこのメッセージと、panic: runtime error: invalid memory address or nil pointer dereference[signal SIGSEGV: segmentation violation ...] で始まるスタックトレースとともにクラッシュします。よくある原因は次のとおりです。

  • 一度も設定されていないポインタ変数や構造体のフィールド
  • map[string]*T での存在しないキーの m[key]nil を返す)
  • エラーの無視:f, _ := os.Open(path) は失敗時に f をnilのままにする
  • nilポインタに対して呼ばれ、フィールドを読むポインタレシーバーのメソッド

nilポインタでメソッドを呼ぶこと自体は正しいコードです。panicが起きるのは、メソッドがレシーバーのフィールドに触れたときだけです。

ポインタを使うべきとき

ポインタを使うのは次のようなときです。

  • 関数やメソッドが呼び出し側の値を変更する必要がある
  • 値が大きな構造体で、頻繁に受け渡される
  • 型をコピーしてはいけない(sync.Mutex を含む、または接続のような一意のリソースを表す)
  • nil で「値がない」ことを表したい。たとえばJSONの構造体の省略可能なフィールドでは、*int が「ない」と 0 を区別する

ポインタを使わないのは次のようなときです。

  • 値が小さく読み取り専用(time.TimePoint
  • 型がすでに参照のような値である。スライス、マップ、チャネル、関数、インターフェースの前に * が必要になることはまれで、*[]int*map[string]int はほぼ常に間違いです

ポインタが自動的に速くなるわけではありません。小さな構造体のコピーは安価で、ポインタはヒープへの確保を強いてガベージコレクタの仕事を増やすことがあります。

ポインタとループ変数

Go 1.22以降、反復ごとに独自の変数があるので、ループ変数のアドレスを取るのは安全です。

これは 0 1 2 と表示します。Go 1.22(range 3 もこのバージョンで入りました)より前は、同じループを for i := 0; i < 3; i++ で書くと、3つのポインタが1つの変数を共有していたので 3 3 3 と表示されました。for _, v := range items での &v は、スライスの中の要素ではなく要素のコピーを指すことに注意してください。要素そのものへのポインタを得るには &items[i] を使います。

よくある間違い

  • チェックせずに参照外しする。 nilになりうる場所では if p == nil をチェックします。
  • rangeの値へのポインタ。 &v はコピーを指します。&s[i] を使います。
  • スライスやマップへのポインタ。 必要になることはまれです。代わりに新しいスライスを返します。
  • append をまたいで &s[i] を保持する。 append が再確保すると、ポインタは古い配列を指したままで、その後の書き込みはどこにも見えない場所に入ります。

よくある質問

Goの&と*は何を意味しますか?

&xx のアドレスを取り、*T 型のポインタを返します。*p はポインタを参照外しし、指している値を読み書きします。型の中の *T は「Tへのポインタ」という意味です。

Goのnewとmakeの違いは何ですか?

new(T) はゼロ値の T を確保して *T を返し、どんな型にも使えます。make はスライス、マップ、チャネルにだけ使え、その型の初期化済みの(ゼロ値でもポインタでもない)値を返します。make(map[string]int) はすぐに使えますが、new(map[string]int) はnilマップへのポインタです。

Goでローカル変数へのポインタを返せますか?

返せます。Cと違って安全です。コンパイラのエスケープ解析が、変数が関数より長く生きることを見抜き、ヒープに確保します。func newInt() *int { x := 5; return &x } は正しいGoのコードです。

Goの「invalid memory address or nil pointer dereference」の原因は何ですか?

nilポインタを通した読み書きです。一度も代入されていない *T の変数、map[K]*V で存在しないキーを参照したとき、チェックしなかったエラーと一緒に nil を返した関数などが原因になります。nilをチェックするか、使う前にポインタを初期化します。

Goにポインタ演算はありますか?

ありません。ポインタに数を足したり、ポインタを通してメモリにインデックスでアクセスしたりはできません。低レベルのコード向けに unsafe パッケージでは可能ですが、安全性の保証はありません。普通のGoのコードでは、連続したメモリにはスライスを使います。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める