🖥

Go言語 – Slice / 配列 を for~range で展開してポインタを append すると全部同じ値になる

に公開

参考

Goにおいてスライスおよびマップをforで回す際には「rangeで取ってきている値は常に同じメモリアドレスに格納される」ようになっています

https://zenn.dev/canalun/articles/go_for_loop_pointer_trap

後述するが、このせいで一部挙動に問題が出るようだ。

TL; DR

あまり深く考えずGolangの仕様と割り切ったら良い気がした。

こちらも後述するがループ内で変数を再定義することで回避できる。

package main

import "fmt"

func main() {
	numbers := []int{1, 2, 3}

	for _, number := range numbers {
		fmt.Println(number)
		fmt.Println(&number)
	}
}

// 結果
// 1
// 0xc0000b2000
// 2
// 0xc0000b2000
// 3
// 0xc0000b2000

この例でいうと number に全て同じアドレスが割り当てられている。
値はそれぞれ別のものをPrint出来るがアドレスは同じ。

は???

同じアドレスが使われている…だから何やねん っていう。

深く考えたら負けというか、追いかけても理解が一筋縄にはいかない気がした。
気持ち的にはGolangの仕様というかバグぐらいって言いたい。

append で困る場合

たとえば各値のポインタで新配列/スライスを作ろうとする場合などに問題が起きそうだ。

package main

import "fmt"

func main() {
	numbers := []int{1, 2, 3}
	copy_numbers := []*int{}

	for _, number := range numbers {
		copy_numbers = append(copy_numbers, &number)
	}

	for _, copy_number := range copy_numbers {
		fmt.Println(*copy_number)
	}

}

// 結果
// 3
// 3
// 3

ループを抜けた後にアドレスを参照して値を確認すると全てが

全てが最後の値を参照してしまうらしい。

https://zenn.dev/muro/articles/c988a58bd48814

この場合の対策

ループ内で変数を再定義すると良いようだ。

package main

import "fmt"

func main() {
	numbers := []int{1, 2, 3}
	copy_numbers := []*int{}

	for _, number := range numbers {
-		copy_numbers = append(copy_numbers, &number)
+		n := number
+		copy_numbers = append(copy_numbers, &n)
	}

	for _, copy_number := range copy_numbers {
		fmt.Println(*copy_number)
	}

}
// 結果
// 1
// 2
// 3

参考

https://pyteyon.hatenablog.com/entry/2021/10/12/032217#range-の仕様の調査

私は for i, n := range sl {...} という for 文を実装したとき、「n にはループ毎に sl[i] が入るから、その値へのポインタも sl[i] へのポインタに等しい。だからループ毎に n へのポインタも書き換わるはず。」と考えていた。しかし、実際にはn はループ用に定義された一時的な変数であり、for 文のブロックに入った時に一度だけメモリが確保され、ループ毎に値が上書きされるだけの変数である。つまり、range によって定義された n へのポインタはループ中は一定である。

チャットメンバー募集

何か質問、悩み事、相談などあればLINEオープンチャットもご利用ください。

https://line.me/ti/g2/eEPltQ6Tzh3pYAZV8JXKZqc7PJ6L0rpm573dcQ

Twitter

https://twitter.com/YumaInaura

公開日時

2022-08-29

Discussion