[Golang] Pointer

2021-10-25 hit count image

Golangでポインタ(Pointer)を使って変数のメモリアドレスを扱う方法について説明します。

概要

今回のブログポストではGolangでPointer(ポインタ)を使って変数のメモリアドレスを扱う方法について説明します。このブログポストで紹介するコードは次のリンクで確認できます。

ポインタ

ポインタ(Pointer)はメモリアドレスを値にするタイプです。Golangでポインタは下記のように宣言することができます。

var 変数名 *タイプ
var p *int

このように宣言したポインタ変数は他の変数のアドレスを保存することができます。他の変数のアドレスは&演算子を使ってアクセスできます。

var a int
var p *int
p = &a
*p = 20

この時、変数のタイプが同じである必要があります。タイプが違う場合コンパイルエラーが発生します。

Golangでポインタ(Pointer)を扱う方法を確認するため、main.goファイルを生成して次のように修正します。

package main

import "fmt"

func main() {
  var a int = 10
  var p *int

  fmt.Println(a)

  p = &a
  fmt.Printf("%v\n", &a)
  fmt.Printf("%v\n", p)

  *p = 20
  fmt.Println(a)
  fmt.Println(*p)
}

これを実行すると次のような結果が表示されます。

# go run main.go
10
0xc00001a0e8
0xc00001a0e8
20
20

実行結果を見ると、変数aとポインタ変数pのメモリアドレスが同じであることが分かります、ポインタ変数の値を変更した時、同じメモリアドレスの変数であるaの値も変更されることが確認できます。

ポインタ変数の基本値

Golangで変数を宣言して値を割り当てないと、変数のタイプのデフォルト値が設定されます。しかし、ポインタ変数はメモリアドレスを割り当てる変数のなので、値を設定しないとnilが割り当てられます。

これを確認するため、main.goファイルを次のように修正します。

package main

import "fmt"

func main() {
  var p *int

  fmt.Println(p)
}

これを実行すると次のような結果を確認できます。

# go run main.go
<nil>

したがって、ポインタ変数を使う場合、次のように変数が割り当てられたか確認することができます。

var p *int
if p != nil {
  fmt.Println("Assigned")
}

関数でポインタ

Golangでは次のようにポインタ変数を活用することができます。

package main

import "fmt"

type Data struct {
  value int
  data  [200]int
}

func ChangeData(arg Data) {
  arg.value = 100
  arg.data[100] = 999
}

func ChangePData(arg *Data) {
  arg.value = 100
  arg.data[100] = 999
}

func main() {
  var data Data
  ChangeData(data)
  fmt.Printf("value = %d\n", data.value)
  fmt.Printf("data[100] = %d\n", data.data[100])

  ChangePData(&data)
  fmt.Printf("value = %d\n", data.value)
  fmt.Printf("data[100] = %d\n", data.data[100])
}

これを実行すると次のような結果が表示されます。

# go run main.go
value = 0
data[100] = 0
value = 100
data[100] = 999

関数でポインタ変数を使うと、渡してもらった変数の値を関数中で直接変更することができます。

構造体ポインタの初期化

Golangで構造体(Struct)ポインタは次のように初期化することができます。

package main

import "fmt"

type Data struct {
  value int
  data  [200]int
}

func main() {
  var data Data
  var p1 *Data = &data
  var p2 *Data = &Data{}

  fmt.Println(*p1)
  fmt.Println(*p2)
}

インスタンス

メモリに割り当てられたデータの実体をインスタンス(instance)と言います。

new関数

次のようにnew関数を使って構造体のインスタンスを生成することができます。

p1 := &Data{}
var p2 = new(Data)

ここでp1, p2はポインタ変数で、*Dataタイプです。

インスタンスが消える時点

Golangでもガベージコレクター(Garbage Collector)が存在して、インスタンスを参照する変数が全てなくなると自動で削除されます。

  • Dangling Pointer: ポインタ変数がもはや有効ではないメモリを参照する時、発生するエラーを指します。
package main

import "fmt"

type User struct {
  Name string
  Age  int
}

func NewUser(name string, age int) *User {
  var u = User{name, age}
  return &u
}

func main() {
  userPointer := NewUser("John", 20)
  fmt.Println(userPointer)
  fmt.Println(userPointer.Age)
  fmt.Println(userPointer.Name)
}

C, C++の観点で見ると変数uはNewUser関数の中にあるので、関数の呼び出しが終わる時点(})で変数が消えます。したがって、消えた変数のメモリを渡して、そのアドレスを貰って使うuserPointerポインタ変数はDangling Pointerエラーが発生します。

func NewUser(name string, age int) *User {
  var u = User{name, age}
  return &u
}

しかし、Golangではmain関数中でuserPointerポインタ変数が変数uのメモリアドレスを参照してあるので、u変数のインスタンスが消えなく、Dangling Pointerエラーが発生しません。GolangはEscape Analysisを使うので、関数からリターンされる変数を検査して、ヒープメモリに保存するので、このような問題が発生しないです。

完了

これでGolangでポインタ(Pointer)を使ってメモリアドレスを参照してそのメモリアドレスの値を使う方法についてみてみました。内容をちょっとまとめると次のようです。

  • インスタンスはメモリに生成されたデータの実体で、ポインタを使ってインスタンスを参照するようにすることができる。
  • 関数をコールする時、ポイントをパラメータで渡して、関数中でインスタンスの値を修正することができる。
  • 使い切ったインスタンスはガベージコレクターが自動で消す。

Golangではポインタ変数を結構使うので、この部分をよく覚えておくと役に立ちます。

私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!

Posts