ループイテレータでポインタセマンティクスを使用すると、同じ値が参照されるのはなぜですか?[複製]

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個のオブジェクトにとって重要な効率が向上します。

遊び場(正しいバージョン)