この記事は 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
が割り当てられていることがわかります。
まとめ
- ポインタ値は値