当在 Golang 中传递值时与传递引用时,map 的奇怪变异现象

huangapple go评论79阅读模式
英文:

Strange mutation of map when passed value vs when passed by reference (Golang)

问题

在第一种情况下,我通过值传递了一个map:

package main

import (
	"fmt"
	"time"
)

func timeMap(z map[string]interface{}) {
	z["updated_at"] = time.Now()
}

func main() {
	foo := map[string]interface{}{
		"Matt": 42,
	}
	timeMap(foo)
	fmt.Println(foo)
}

输出结果是一个被修改的map:

map[updated_at:2009-11-10 23:00:00 +0000 UTC Matt:42]

在第二种情况下,代码几乎相同,只是通过引用传递:

package main

import (
	"fmt"
	"time"
)

func timeMap(z *map[string]interface{}) {
	(*z)["updated_at"] = time.Now()
}

func main() {
	foo := map[string]interface{}{
		"Matt": 42,
	}
	timeMap(&foo)
	fmt.Println(foo)
}

显然,结果不同:

map[Matt:42 updated_at:2009-11-10 23:00:00 +0000 UTC]

我的期望是:

  • 通过值传递时,map不会被修改。
  • 通过引用传递时,map会被修改,就像第二种情况一样。

然而,在第一种情况下,map被修改了,但顺序与第二种情况相反。

为什么会这样呢?

英文:

In the first case I pass a map to by value:
package main

import (
	"fmt"
	"time"
)

func timeMap(z map[string]interface{}) {
	z["updated_at"] = time.Now()
}

func main() {
	foo := map[string]interface{}{
		"Matt": 42,
	}
	timeMap(foo)
	fmt.Println(foo)
}

The output is a muted map:

map[updated_at:2009-11-10 23:00:00 +0000 UTC Matt:42]

In the second case the code is almost identical but for passing by reference:

package main

import (
	"fmt"
	"time"
)

func timeMap(z *map[string]interface{}) {
	(*z)["updated_at"] = time.Now()
}

func main() {
	foo := map[string]interface{}{
		"Matt": 42,
	}
	timeMap(&foo)
	fmt.Println(foo)
}

Obviously, the result differs:

map[Matt:42 updated_at:2009-11-10 23:00:00 +0000 UTC]

My expectations were the following:

  • when passing by value map is not muted
  • When passing by reference map is muted like in the second case.
    However, in the first case map was muted but in the reverse order (compared to the second case).

Why does it happen so?

答案1

得分: 5

在Go语言中,不存在按引用传递的概念。无论你传递什么(指针、切片头部、映射),它都是按值传递的。问题在于究竟传递的是什么值(即类型的实际“值”是什么)。

当你传递一个映射时,你传递的是其头部的指针的副本,该头部包含指向桶的指针集合,就像哈希表的实现一样。因此,将指针传递给映射很少有意义,因为复制映射头部指针的操作非常廉价。

至于为什么顺序不同,这仅仅是由于映射的内部实现方式,遍历键的顺序是随机的。这只是实现细节。

编辑:

正如@icza正确指出的,传递映射实际上是传递指向映射头部的指针的副本,而不是映射头部本身。对于造成的混淆,我表示抱歉。

英文:

There is no such thing as passing by reference in Go. Whenever you pass anything (pointer, slice header, map) it is always passed by value. The question is what exactly is being passed by value (i.e. what is the actual value of the type).

When u pass a map, you pass a copy of the pointer to its header, which contains a set pointers to the buckets, as in the implementation of the HashTable. https://github.com/golang/go/blob/master/src/runtime/hashmap.go#L106

Therefore it rarely makes sense to pass a pointer to the map, because the operation of copying a map header pointer is extremely cheap.

Now why the order is different, this is simply due to internal implementation of the map, ranging over the keys occurs in a random fashion. Again this is just an implementation details.

EDIT:

As @icza correctly pointed out, passing a map is actually passing a copy of a pointer to the map header, not the map header itself. Sorry for the confusion

huangapple
  • 本文由 发表于 2017年6月12日 01:34:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/44486749.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定