参考
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オープンチャットもご利用ください。