ループイテレータでポインタセマンティクスを使用すると、同じ値が参照されるのはなぜですか?[複製]
Nov 24 2020
複数のレイヤーで発生したバグのあるケースを示す簡単な例:
// Store a list of boxed integers in map and print them.
// This prints 3 times the int value of 3.
type number struct{ val int }
func setInMap() map[int]*number {
list := []number{{1}, {2}, {3}}
m := make(map[int]*number)
for i, n := range list {
// when saving the address of n,
// all values point -> 3
m[i] = &n
}
return m
}
func main() {
m := setInMap()
for _, n := range m {
// Prints 3,3,3 or last set value when pointer
// instead of 1,2,3
fmt.Print(n.val, ",")
}
}
バギーポインターセマンティクスプレイグラウンドリンク
これを、正しく機能する値セマンティックバージョンを保存することと比較してください。
for i, n := range list {
// set -> map using value semantics
// prints all values
m[i] = n
}
バリューセマンティクスプレイグラウンドリンク
回答
Mario Nov 24 2020 at 17:29
その理由は、for..range
コンストラクトが値セマンティクスを使用するため、コンパイラーは各リスト値をスタック、つまりintのコンテンツにコピーしますn
。
// n is copied into the stack at each iteration
for i, n := range list
後でnのアドレスを取得するときに、同じスタック反復変数のアドレスを格納します。
// when saving the address of n,
// in a for..range loop we are taking the address of
// same iterated variable n in stack.
// resulting in the same value stored thrice in the map.
m[i] = &n
ポインタセマンティックバージョンを修正するにはfor i=
、次の正しいバージョンを使用します。
// n is now pointing to distinct
// members of the list in heap.
for i := 0; i < len(list); i++ {
n := &list[i]
m[i] = n
}
副次的なパフォーマンスの注意として、これにより、階層的なネストされたオブジェクトを保持するn個のオブジェクトの非効率的で公平なコピーがなくなるため、大きなn個のオブジェクトにとって重要な効率が向上します。
遊び場(正しいバージョン)