Strange golang “append” behavior (overwriting values in slice)

和自甴很熟 提交于 2019-12-12 07:56:18

问题


I have this simple code:

import "fmt"

type Foo struct {
    val int
}

func main() {
    var a = make([]*Foo, 1)
    a[0] = &Foo{0}

    var b = [3]Foo{Foo{1}, Foo{2}, Foo{3}}
    for _, e := range b {
        a = append(a, &e)
    }

    for _, e := range a {
        fmt.Printf("%v ", *e)
    }
}

I was expecting it to print {0} {1} {2} {3}, however it prints {0} {3} {3} {3}. What happened here?


回答1:


This is because in the for loop you operate with a copy and not with the slice/array element itself.

The for ... range makes a copy of the elements it loops over, and you append the address of this temporary, loop variable - which is the same in all iterations. So you add the same pointer 3 times. And this temporary variable will be set to Foo{3} in the last iteration (last element of the array), so that's why you see that printed 3 times.

Fix: do not add the address of the loop variable, but the address of the array element:

for i := range b {
    a = append(a, &b[i])
}

Output (try it on the Go Playground):

{0} {1} {2} {3} 

See possible duplicate Assigned pointer field becomes <nil>.

Reasoning for this behavior

In Go there are pointer types and non-pointer types, but no "references" (in the meaning it is used in C++ and Java). Given the fact that there are no "reference" types in Go, this is not an unexpected behavior. The loop variable is just an "ordinary" local variable, it can only hold a value (which may be a pointer or non-pointer), but not a reference.

Excerpt from this answer:

Pointers are values just like let's say int numbers. The difference is the interpretation of that value: pointers are interpreted as memory addresses, and ints are interpreted as integer numbers.

When you want to change the value of a variable of type int, you pass a pointer to that int which is of type *int, and you modify the pointed object: *i = newvalue (the value assigned is an int).

Same goes with pointers: when you want to change the value of a variable of pointer type *int, you pass a pointer to that *int which is of type **int and you modify the pointed object: *i = &newvalue (the value assigned is an *int).

All in all, the loop variable is just a normal variable having the element type of the array/slice you're looping over, and for it to have the value of the actual iteration, the value must be assigned to it which copies the value. It is overwritten in the next iteration.




回答2:


Because you are appending the slice using as a pointer reference actually you are not creating new entries in the slice but updating every time the last entry. Changing the code from pointer reference to normal value reference it will work.

package main

import "fmt"

type Foo struct {
    val int
}

func main() {
    var a = make([]Foo, 1)
    a[0] = Foo{0}

    var b = [3]Foo{Foo{1}, Foo{2}, Foo{3}}
    for _, e := range b {
        a = append(a, e)
    }

    for i, e := range a {
        fmt.Printf("%d: %v\n", i, e)
    }
}

Working code on Go playground.




回答3:


On each iteration of your loop, the value of e changes, yet each time you're passing a pointer to e into the slice. So you end up with a slice containing 3 pointers to the same value.

You can also make a copy of the value and pass its pointer. Since on each iteration, you're making a new copy of the value, the pointers passed to the slice will point to different values:

var a = make([]*Foo, 1)
a[0] = &Foo{0}

var b = [3]Foo{Foo{1}, Foo{2}, Foo{3}}
for _, e := range b {
    eCopy := e
    a = append(a, &eCopy)
}

https://play.golang.org/p/VKLvNePU9af



来源:https://stackoverflow.com/questions/38692998/strange-golang-append-behavior-overwriting-values-in-slice

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!