Goのポインタのポインタ


この記事は Go7 Advent Calendar 2019 の 6 日目の記事です。

はじめに

Go ではポインタ型としてポインタを扱うことができます。Go のポインタのポインタの Tips を共有します。Devquizです。

Q: 以下はの標準出力はいずれも **Num 型の値を表示しますが、1, 2, 3 それぞれで何がどのように表示されるでしょうか??

package main

import (
    "fmt"
)

type Num struct {
    i int
}

func main() {
    np := &Num{i: 3}

    // 1: main 関数での出力
    fmt.Printf("%p\n", &np)

    // 2: pointer 関数での出力
    pointer(np)

    // 3: pointerpointer 関数での出力
    pointerpointer(&np)
}

func pointer(np *Num) {
    fmt.Printf("%p\n", &np)
}

func pointerpointer(npp **Num) {
    fmt.Printf("%p\n", npp)
}

 
 
 
 
 
 
 
 
 
 
  
 
 
 
 
 
 
 
 
  
 
 
 
 
 
 
 
 
  
 
 
 
 
 
 
 
 
  
 
 
 
 
 
 
 
 
  
 
 
 
 
 
 
 
 
 
 
 
 
  
 
 
 
 
 
 

答え

0x40c138
0x40c148
0x40c138

**Num 型だけど全部同じアドレスではないです。(それはそうなのですが)

何を表示しているのか

何を表示しているのか順番に確認していきましょう。

1: main 関数での出力

まず np := &Num{i: 3} についてです。これは無名変数 Num{i: 3} へのポインタです。コンパイラによって最適化されない限り、仮想メモリ上に確保されます、メモリ構造を簡易的に図にすると以下になっています。

Num{i: 3} もメモリ上に確保されています。Go Playground で fmt.Printf("%p\n", np) としてポインタのアドレスを確認してみます。

func main() {
    np := &Num{i: 3}
    fmt.Printf("%p\n", &np)
+   fmt.Printf("%p\n", np) // 0x40e020
    pointer(np)
    pointerpointer(&np)
}

すると 0x40e020 であることが分かります。

よって以下では **Num 型の変数 0x40c138 が出力されることになります。

fmt.Printf("%p\n", &np)

2: pointer 関数での出力

以下の関数について考えてみます。ここでは *Num を引数として渡しています。*Num は何だったかというと Num のアドレスを保持している変数でした。ポインターで示している アドレスは値で渡されます。よってアドレスの値を格納する変数は、元の変数を格納しているアドレスとは別にメモリ上に確保されます。

よって以下では 0x40c138 ではなく別のアドレス値 0x40c148 が出力されています。

func pointer(np *Num) {
    fmt.Printf("%p\n", &np)
}

3: pointerpointer 関数での出力

以下は Num のポインターへのポインターでした。図にするとわかりやすいです。

func pointerpointer(npp **Num) {
    fmt.Printf("%p\n", npp)
}

この **Num の値を格納する変数もメモリ上に確保されているので、そのアドレスを表示することができます。これも Go Playground に追加して確認しておきます。

func pointerpointer(npp **Num) {
    fmt.Printf("%p\n", npp)
+   fmt.Printf("%p\n", &npp) // 0x40c150
}

すると 0x40c150 であることが分かります。つまり以下です。こちらも 2 の場合と同様にもとのアドレス 0x40c138 とは別のアドレス 0x40c150 が割り当てられていることがわかります。

まとめ

  • ポインタ値は値

参考