LoginSignup
704
469

More than 5 years have passed since last update.

Goで学ぶポインタとアドレス

Last updated at Posted at 2018-04-27

Goで学ぶポインタとアドレス

Goってシンプルで書きやすいですよね。
しかし、シンプルなGoでもいくつか躓きやすいポイントがあると思っています。
その最初のポイントがポインタではないでしょうか。特に、ポインタの概念が存在しない言語から始めた人にとっては、なかなかとっつきにくいものだと思います。そこで今回は、なんとなく使っていたポインタを、ちゃんと理解するためのエントリを書きました。ポインタをちゃんと理解しようとすると、その前提として知らなければならないことが多々あり、そこから説明するので、やや遠回りをした説明になっています。
「これちげえじゃねえか」とか、「ここわかりにくいぜ」っていうのがあったら、ご教授ください。

※ 技術的な話は「です、ます」調よりも「である、だ」調の方が書きやすいので、以降は「である、だ」調で書きます。

前提知識Part

先ほど述べたとおり、ポインタを理解しようとすると、前提知識が必要になってくる。
まずは、その前提知識を説明したいと思う。

プログラムのコンパイルから実行までの流れ

何かしらの高級言語(GoとかJavaとか)で書かれたソースコードはそのままではそのプログラムをPCで実行することはできない。
ではどうするかというと、高級言語で書かれたソースコードをコンパイラでコンパイルし、コンピュータがプログラムを実行できるような形にする。
この「実行できるような形」は、バイナリーコードになった実行ファイルである。

変数とメモリとアドレス

ポインタを理解するには、まず変数とメモリとアドレスの関係を理解する必要がある。
ここで整理したいと思う。

  • メモリは、1バイト毎に番号がつけられ、区別されている
  • 変数は実行ファイルになると、番号が割り当てられる
  • 変数は、メモリ上の該当の番号の区分に格納され、記憶される
  • この変数に付与されるメモリの区分番号をアドレスという

図にするとこんな感じ
メモリと変数.png

ここでいうメモリ1番地とかがアドレスで、実際にはあとで説明するが、0x1040a0d0 みたいな感じの16進数で表される。

参考 : 変数とメモリの関係 - 苦しんで覚えるC言語

例えば、以下の様にする。

name := "太郎"

そうすると、コンパイルした時に、メモリ上のある場所に変数の値が格納される。
この メモリ上のある場所 が上記で説明した アドレス というものである。
メモリ上に変数が格納される場所がアドレスである。

実際に格納されたアドレスを16進数で表示させることもできる。
詳しくはここを参照。

package main

import "fmt"

// Person は人間を表す構造体。
type Person struct {
    Name string
    Age  int
}

func main() {
    // ポインタ型の変数を宣言する
    // pがポインタ変数
    // *Personポインタ型
    var p *Person

    p = &Person{
        Name: "太郎",
        Age:  20,
    }
    fmt.Printf("変数pに格納されているアドレス :%p", p)
}

実行結果

変数pに格納されているアドレス :0x1040a0d0

参考 : メモリの仕組み - 苦しんで覚えるC言語

ポインタPart

ポインタ型とポインタ変数

ポインタという概念を学ぶ時に、よく以下のような説明を目にする。

  • ポインタってのはメモリのアドレス情報のことだよ
  • ポインタってのはアドレス情報を格納するための変数のことだよ

これらの説明はわかりやすいのだが、実際にコードを見た時には「結局どれがポインタなの?」ってなりがちだ。
その疑問ついて以下の記事が非常にわかりやすかったので、一読されるといいと思う。
C言語のポインタきらい - Qiita

上記の記事によれば、以下のコードの pが ポインタ変数 で、 *Person がポインタ型になる。

コード例

package main

import "fmt"

// Person は人間を表す構造体。
type Person struct {
    Name string
    Age  int
}

func main() {
    // ポインタ型の変数を宣言する
    // pがポインタ型変数
    // *Personポインタ型
    var p *Person

    p = &Person{
        Name: "太郎",
        Age:  20,
    }
    fmt.Printf("p :%+v\n", p)
    fmt.Printf("変数pに格納されているアドレス :%p", p)
}

実行結果

p :&{Name:太郎 Age:20}
変数pに格納されているアドレス :0x1040a0d0

pを表示すると、 &{Name:太郎 Age:20} となることを覚えておいて欲しい。
& については後ほど説明する。

ポインタ変数とは

メモリ上のアドレスを値として入れられる変数のこと

引用元 : ポインタ変数とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典

上記のコードでは、変数pがポインタ変数となり、実際にpにはアドレスが格納されている。(詳細は後述)

デリファレンス

& を使うことで、ポインタ型を生成することができる。
Person型の変数pを &p とすると、Personへのポインタである *Person型 の値を生み出すことができる。
&p は、pのアドレスという。

package main

import "fmt"

// Person は人間を表す構造体。
type Person struct {
    Name string
    Age  int
}

func main() {

    // 値として、pに代入
    p := Person{
        Name: "太郎",
        Age:  20,
    }

    fmt.Printf("最初のp :%+v\n", p)

    p2 := p
    p2.Name = "二郎"
    p2.Age = 21
    // pではなく
    fmt.Printf("p2で二郎に書き換えを行なったはずのp :%+v\n", p)

    // &pで*Person(Personのポインタ型)を生成する
    // p3はpのアドレスが格納されている状態になる
    p3 := &p
    p3.Name = "二郎"
    p3.Age = 21

    fmt.Printf("p3で二郎に書き換えを行なったp :%+v\n", p)
}

実行結果

最初のp :{Name:太郎 Age:20}
p2で二郎に書き換えを行なったはずのp :{Name:太郎 Age:20}
p3で二郎に書き換えを行なったp :{Name:二郎 Age:21}

pはポインタではなく、Person型の値である。
p2 := p は、Person型の値コピーしてp2に格納しているので、p2で書き換えを行っても、それがpに反映されることはない。これを値渡しという。

逆に、p3 := &p は、*Person型(Personへのポインタである *Person型)をp3に格納しているので、p3はpのアドレス(Personへのポインタである *Person型)を持っていることになる。
従って、p3で書き換えを行うと、その変更はpに反映される。これを参照渡しという。

Goでは、構造体内のメソッド内で、構造体のフィールドの情報を変更するときには、この参照渡しをよく利用する。こことかが参考になる。

*Hoge型が格納された変数

& を使うことで、ポインタ型を生成することができた。
では、& を使って生成されたポインタ型を格納した変数はどう扱うか。
まずは、& の復習もかねて、以下のコードを見てみよう。

package main

import "fmt"

func main() {
    name := "太郎"
    fmt.Printf("name :%v\n", name)

    namePoint := &name

    // namePointは、&nameが格納されているだけなので、stringへのポインタである *string型の値が格納されている。
    fmt.Printf("namePoint :%v\n", namePoint)

    // namePointが指している変数は、"*namePoint"という感じで、"*"をつけて表す。
    fmt.Printf("namePoint :%v\n", *namePoint)
}

実行結果

name :太郎
namePoint :0x1040c128
namePoint :太郎

コードに示したように namePoint には &name が格納されている。
& は、ポインタ型を生成するので&name は、stringへのポインタである *string型 の値(アドレス)が格納されている。
よって、 namePoint を表示すると *string型 の値である name のアドレスが格納されていることがわかる。

では、namePoint の元となっている name の変数に格納されている値(ここでは「太郎」)は、どのように取得すれば良いか。
そのような場合は、 *namePoint のように変数名の前に * をつければ良い。
なお、ここが紛らわしいところなのだが、 *namePoint 自体も変数なので、これに代入することもできる。
例えば、以下のようなコードだ。

package main

import "fmt"

func main() {
    name := "太郎"
    fmt.Printf("name :%v\n", name)

    namePoint := &name

    // namePointは、&nameが格納されているだけなので、stringへのポインタである*string型の値が格納されている。
    fmt.Printf("namePoint :%v\n", namePoint)

    // namePointが指している変数は、"*namePoint"という感じで、"*"をつけて表す。
    fmt.Printf("namePoint :%v\n", *namePoint)

    *namePoint = "二郎"

    // *namePointに値を代入することもできる。
    fmt.Printf("*namePointに二郎を代入後の*namePoint :%v\n", *namePoint)

    // 再代入したところで、namePointに格納されている*string型の値(アドレス)自体は、変わらない
    fmt.Printf("*namePointに二郎を代入後のnamePoint :%v\n", namePoint)

    // stringへのポインタである*string型の値(nameに格納されている値)を書き換えたので、nameの値も変更される。
    fmt.Printf("*namePointに二郎を代入後のname :%v\n", name)
}

実行結果

name :太郎
namePoint :0x1040c128
namePoint :太郎
*namePointに二郎を代入後の*namePoint :二郎
*namePointに二郎を代入後のnamePoint :0x1040c128
*namePointに二郎を代入後のname :二郎

ここで注意すべきことは、
*namePoint に値を代入すると、nameの値も書き変わるということだ。
これはなぜか?
*namePoint には、 &name (stringへのポインタである*string型の値が格納されているからであり、それを *namePoint = "二郎" で書き換えているので、当然 name の値も書き変わるということである。

まとめ

ポインタは確かにとっつきにくいかもしれないですが、Goを使用する上では必須ですし、使い方によっては非常に便利なものなので、ちゃんと理解して使っていきましょう。

参考

参考文献

松尾 愛賀 (2016/4/15)『スターティングGo言語』 翔泳社

Alan A.A. Donovan (著), Brian W. Kernighan (著), 柴田 芳樹 (翻訳)(2016/6/20)『プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)』丸善出版

参考にさせていただいたサイト

バイナリーコード(ばいなりーこーど)とは - コトバンク

変数とメモリの関係 - 苦しんで覚えるC言語

メモリの仕組み - 苦しんで覚えるC言語

C言語のポインタきらい - Qiita

ポインタ変数とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典

Part4 誰もがつまずくポインタを完璧理解 | 日経 xTECH(クロステック)

C言語ポインタの基礎 - Qiita

Goのポインタ - はじめてのGo言語

【C言語入門】ポインタのわかりやすい使い方(配列、関数、構造体) | 侍エンジニア塾ブログ | プログラミング入門者向け学習情報サイト

もう一度基礎からC言語 第38回 プログラミングの周辺事項(1)~Cで書いたプログラムの仕組みと構造 Cプログラムの構造

Go言語の構造体の値渡しとポインタ渡しの動作を確認してみる - Qiita

704
469
3

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
704
469