【发布时间】:2020-03-23 15:38:36
【问题描述】:
当将一个 big.Int 的实例指向另一个实例时,我遇到了 go 的 big.Int 的一些无法解释的行为。
我知道,为了将bit.Int 的实例的值设置为另一个,必须使用Int.SetXXX 设置器,因为它们实际上导致big.Int 中的底层abs 切片被复制到一个新的分配的数组。但是,暂时搁置一下,我想知道为什么会发生以下行为。
考虑以下几点:
错误示例(基础值发生变化):
func main() {
v1p := big.NewInt(1)
v2p := big.NewInt(2)
v1 := *v1p
v2 := *v2p
v2 = v1
v1.SetInt64(3)
fmt.Println(v1.Int64(), v2.Int64())
}
(在此处运行:https://play.golang.org/p/WxAbmGdKG9b)
正确示例(值不会发生变化):
func main() {
v1p := big.NewInt(1)
v2p := big.NewInt(2)
v1 := *v1p
v2 := *v2p
v2.Set(v1p)
v1.SetInt64(3)
fmt.Println(v1.Int64(), v2.Int64())
}
(在此处运行:https://play.golang.org/p/16qsGhwHIWf)
如果我理解正确,以下内容应该基本上说明了错误示例中会发生什么:
func main() {
var a, b *int // analogous to the 2 big.Int pointers returned
c, d := 3, 3
a = &c // we set the pointers to point to something we can then dereference
b = &d
e := *a // e and f should now point to the values pointed to by the pointers
f := *b
// the rest is self-explanatory
e = f
c = 5
d = 4
fmt.Println(a, b, c, d, e, f)
}
(在此处运行:https://play.golang.org/p/cx76bnmJhG7)
我唯一的假设是,在 Wrong 示例中以某种方式将结构内容复制到 v2 时,abs 切片不会被深度复制,而是它的存储引用实际上与v1 中的切片指向的存储相同。
真的会这样吗?这也是符合语言规范的预期行为吗?
【问题讨论】:
-
在第一个示例中,v1 和 v2 都将在
v2 = v1之后引用 v1p,因此您没有改变v2p,而是打印了两次v1p。 (这是 100% 的猜测,因为我知道的很少)。见play.golang.org/p/GgdhXXNdB85 -
你永远不应该取消引用
*big.Int,所以这永远不会成为问题。big.Int包含一个内部切片(这意味着一个指针),这就是您要复制的内容。 -
将
v1分配给v2会复制结构值,其中包括nat字段,它是一个切片。切片是类似结构的标头,包含指向后备数组的指针。复制切片只是复制标题,包括指针,并且副本将指向同一个后备数组。查看详情:Are golang slices passed by value? 和 How to inspect slice header? -
正如您正确猜测的那样,Go 中的任何副本都不是深层副本。任何包含切片、指针、通道或闭包的结构都不能被深度复制(除非它提供了这样做的函数/方法)。
-
展望未来,发生的事情可能取决于实现。由于您现在有 2 个结构值(不是指针),因此调用一个对其进行变异的方法,这 2 个结构值可能不一致。您调用该方法的那个可能会更新切片元素和其他结构字段。其他更改的结构字段不会反映在其他结构上,切片元素的更改可能会也可能不会(例如,如果切片使用
append()扩展,则会创建一个新的切片头但其他结构不会看到这一点;但是如果当前元素发生变化,那将是)。