0
1

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-08-29

参考

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

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

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

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

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

この場合の対策

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

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

参考

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

チャットメンバー募集

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

Twitter

0
1
1

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
0
1