Simple minds think alike

より多くの可能性を

【Golang】for-rangeでポインタを使うと同じ値になる理由

Go言語を使っているとfor-rangeループを多用します。その際、ポインタが絡むと予期しない結果になることがあるので、その事象の発生理由と対策をまとめてみたいと思います。

事象

Aさん(56歳)、Bさん(33歳)、Cさん(41歳)、Dさん(22歳)が配列で定義されていたとして、年齢が50歳以下の人だけを別の配列に入れるというコードを以下のように書いてみたとします。

package main

import "fmt"

type Person struct {
    name string
    age  int
}

func main() {
    people := []Person{
        Person{"Aさん", 56},
        Person{"Bさん", 33},
        Person{"Cさん", 41},
        Person{"Dさん", 22},
    }
    personPtrs := []*Person{}

    for _, person := range people {
        fmt.Printf("人の名前: <%s>\n", person.name)
        if person.age <= 50 {
            personPtrs = append(personPtrs, &person)
        }
    }

    for _, personPtr := range personPtrs {
        fmt.Printf("ポインタの参照先の名前 <%s>\n", personPtr.name)
    }
}

このコードを実行して期待する結果は、ポインタの参照先の名前に年齢が50歳以下のBさん、Cさん、Dさんの3名が表示されることですが、実際にコードを実行してみるとDさんの名前だけが3回表示されます。

人の名前: <Aさん>
人の名前: <Bさん>
人の名前: <Cさん>
人の名前: <Dさん>

ポインタの参照先の名前 <Dさん>
ポインタの参照先の名前 <Dさん>
ポインタの参照先の名前 <Dさん>

何が問題か

このコードの挙動を把握するために、変数のポインタを一緒に表示するようにコードを直してみます。

package main

import "fmt"

type Person struct {
    name string
    age  int
}

func main() {
    people := []Person{
        Person{"Aさん", 56},
        Person{"Bさん", 33},
        Person{"Cさん", 41},
        Person{"Dさん", 22},
    }
    personPtrs := []*Person{}

    for _, person := range people {
        fmt.Printf("人の名前: <%s>、ポインタ: <%p>\n", 
            person.name, &person)
        if person.age < 50 {
            personPtrs = append(personPtrs, &person)
        }
    }

    for _, personPtr := range personPtrs {
        fmt.Printf("ポインタの参照先の名前 <%s>、ポインタ: <%p>\n", 
            personPtr.name, personPtr)
    }
}

実行してみると以下のように出力されました。

人の名前: <Aさん>、ポインタ: <0xc0000a8018>
人の名前: <Bさん>、ポインタ: <0xc0000a8018>
人の名前: <Cさん>、ポインタ: <0xc0000a8018>
人の名前: <Dさん>、ポインタ: <0xc0000a8018>

ポインタの参照先の名前 <Dさん>、ポインタ: <0xc0000a8018>
ポインタの参照先の名前 <Dさん>、ポインタ: <0xc0000a8018>
ポインタの参照先の名前 <Dさん>、ポインタ: <0xc0000a8018>

この出力内容から for _, person := range people { のループの度に変数person を新しく生成しメモリ領域を確保しているのではなく、以下のように1つの変数に代入し、内容を上書いているだけだということが分かります。

package main

import "fmt"

type Person struct {
    name string
    age  int
}

func main() {
    people := []Person{
        Person{"Aさん", 56},
        Person{"Bさん", 33},
        Person{"Cさん", 41},
        Person{"Dさん", 22},
    }

    // 変数(入れ物)は1個だけ
    var person Person

    // 1回目のループ
    person = people[0]
    fmt.Printf("人の名前: <%s>、ポインタ: <%p>\n", person.name, &person)

    // 2回目のループ
    person = people[1]
    fmt.Printf("人の名前: <%s>、ポインタ: <%p>\n", person.name, &person)

    // 3回目のループ
    person = people[2]
    fmt.Printf("人の名前: <%s>、ポインタ: <%p>\n", person.name, &person)

    // 4回目のループ
    person = people[3]
    fmt.Printf("人の名前: <%s>、ポインタ: <%p>\n", person.name, &person)
}
人の名前: <Aさん>、ポインタ: <0xc0000a8018>
人の名前: <Bさん>、ポインタ: <0xc0000a8018>
人の名前: <Cさん>、ポインタ: <0xc0000a8018>
人の名前: <Dさん>、ポインタ: <0xc0000a8018>

最終的に変数 person (ポインタ 0xc0000a8018) には"Dさん"の情報しか入っていないので、最初のコードのpersonPtrs の各値である personのポインタ(0xc0000a8018) を参照しても "Dさん"の情報しか出力されなかったというわけです。

対処方法

期待する結果(Bさん、Cさん、Dさんの3名の名前が表示される)にするにはコードをどのように修正すれば良いでしょうか。

対処方法は2つあるかと思います。ループ変数( person )のポインタを使わないという点はどちらも共通しています。

①元の配列の要素のポインタを使う

package main

import "fmt"

type Person struct {
    name string
    age  int
}

func main() {
    people := []Person{
        Person{"Aさん", 56},
        Person{"Bさん", 33},
        Person{"Cさん", 41},
        Person{"Dさん", 22},
    }
    personPtrs := []*Person{}

    for i := 0; i < len(people); i++ {
        var person *Person
        person = &people[i] // 元の配列の要素のポインタを代入
        fmt.Printf("人の名前: <%s>、ポインタ: <%p>\n",
            person.name, person)
        if person.age <= 50 {
            personPtrs = append(personPtrs, person)
        }
    }

    for _, personPtr := range personPtrs {
        fmt.Printf("ポインタの参照先の名前 <%s>、ポインタ: <%p>\n",
            personPtr.name, personPtr)
    }
}

以下の実行結果を見ると personPtrs の各値が元の配列( people )の各要素のポインタと同じ(0xc000062198, 0xc0000621b0, 0xc0000621c8)になっていることが分かります。

人の名前: <Aさん>、ポインタ: <0xc000062180>
人の名前: <Bさん>、ポインタ: <0xc000062198>
人の名前: <Cさん>、ポインタ: <0xc0000621b0>
人の名前: <Dさん>、ポインタ: <0xc0000621c8>

ポインタの参照先の名前 <Bさん>、ポインタ: <0xc000062198>
ポインタの参照先の名前 <Cさん>、ポインタ: <0xc0000621b0>
ポインタの参照先の名前 <Dさん>、ポインタ: <0xc0000621c8>

メリット・デメリット

この方法のメリット・デメリットは以下の通りです。

  • メリット
    • 新しい変数のメモリ領域を確保しないので処理が速い
  • デメリット
    • 新しい変数のメモリ領域を確保しないのでpersonPtrs の参照先の値を書き換えると元の配列の値まで変わってしまう

②ループ毎に別の変数を定義し、そのポインタを使う

別の変数を定義することによりループ毎にメモリ領域を確保し、そのポインタを personPtrs に入れます。

package main

import "fmt"

type Person struct {
    name string
    age  int
}

func main() {
    people := []Person{
        Person{"Aさん", 56},
        Person{"Bさん", 33},
        Person{"Cさん", 41},
        Person{"Dさん", 22},
    }
    personPtrs := []*Person{}

    for i, person := range people {
        personTmp := person // ループ毎に別の変数を定義
        fmt.Printf("人の名前: <%s>、ポインタ: <%p>、元の配列の要素のポインタ: <%p>\n",
            personTmp.name, &personTmp, &people[i])
        if personTmp.age <= 50 {
            personPtrs = append(personPtrs, &personTmp)
        }
    }

    for _, personPtr := range personPtrs {
        fmt.Printf("ポインタの参照先の名前 <%s>、ポインタ: <%p>\n",
            personPtr.name, personPtr)
    }
}

以下の実行結果を見ると、ループ毎に変数のポインタが変っていることが分かります。

人の名前: <Aさん>、別変数のポインタ: <0xc000010030>、元の配列の要素のポインタ: <0xc000062180>
人の名前: <Bさん>、別変数のポインタ: <0xc000010048>、元の配列の要素のポインタ: <0xc000062198>
人の名前: <Cさん>、別変数のポインタ: <0xc000010060>、元の配列の要素のポインタ: <0xc0000621b0>
人の名前: <Dさん>、別変数のポインタ: <0xc000010078>、元の配列の要素のポインタ: <0xc0000621c8>

ポインタの参照先の名前 <Bさん>、ポインタ: <0xc000010048>
ポインタの参照先の名前 <Cさん>、ポインタ: <0xc000010060>
ポインタの参照先の名前 <Dさん>、ポインタ: <0xc000010078>

メリット・デメリット

この方法のメリット・デメリットは以下の通りです。

  • メリット
    • 新しい変数のメモリ領域を確保するので処理が遅い
  • デメリット
    • 新しい変数のメモリ領域を確保するのでpersonPtrsの参照先の値を書き換えても元の配列の値は変わらない

結論

ループの回数がさほど多くない場合には、実行速度はさほど気にしなくても良いので ②ループ毎に別の変数を定義し、そのポインタを使う という方法が良さそうです。

実行速度に対してセンシティブな用途やループ回数が多い場合は、 ①元の配列の要素のポインタを使う という方法が良さそうです。

参考資料