【问题标题】:How to keep a strong reference in Go?如何在 Go 中保持强引用?
【发布时间】:2015-09-01 18:47:17
【问题描述】:

有什么方法可以在 Go 中保持强引用?

鉴于以下令人费解的代码:

package main

import (
  "fmt"
)

func main() {   
  slice := make([]int, 5)
  slice[3] = 25 // whatever index between 0 and 4 included I don't care

  slicesArray := make([]*[]int, 2)  
  slicesArray[0] = &slice

  fmt.Println((*(slicesArray[0]))[3])
  slice = nil
  fmt.Println((*(slicesArray[0]))[3])
}

这个程序当然会崩溃,因为一旦将 slice 设置为 nil,垃圾收集器会将内存区域标记为脏。

但是有没有办法告诉 Go 我的切片指针切片应该保持对这些切片的强引用? 此外,保留对切片的引用而不是将 slicesArray 声明为[][]int 是否有任何内存使用增益?是否有任何文档明确说明这应该如何工作?

【问题讨论】:

  • “因为一旦将 slice 设置为 nil,垃圾收集器就会将内存区域标记为脏”,这是不正确的。如果任何代码都有指向某个东西的指针,那么垃圾收集器就会不理会它。您可以整天设置指向nil 的指针,而垃圾收集器不会发生任何事情(除非 指向的内容现在不再被其他任何东西指向)。

标签: arrays pointers go slice


【解决方案1】:

TL;DR:(摘要)

您只需要复制切片值即可。 slice 类型的值是 底层 数组的描述符:

slice2 := slice

现在slice2 将引用相同的共享底层数组,因此在slice 归零后即可访问。

长篇大论:

slice := make([]int, 5)

这将创建一个名为 slice 的局部变量,其类型为 []int(并将使用引用 make 在后台创建的大小为 5 的数组的描述符进行初始化)。它是一个切片,它是在后台自动创建的底层数组的连续部分的描述符。

slicesArray[0] = &slice

这会将局部变量(名为slice)的地址存储到slicesArray 的第0 个元素中。请注意,slicesArray 只是一个指针切片(这些指针可能指向 []int 类型的值,但现在这无关紧要)。

所以仍然没有创建原始slice 的副本。

因此,当您将类型为 []int 的唯一值(即名为 slice 的局部变量)归零时,您将唯一的切片值归零(并且您将失去对其支持数组的唯一引用)。

您想在slice 清零后保留切片值吗?只需复制它(切片值)。复制切片类型的值只会复制描述符,不复制后备数组;并且副本将引用相同的支持数组(它是共享的):

slice2 := slice // makes a copy of the slice value
slice = nil

在此之后*(slicesArray[0]) 仍将指向一个切片值为nil,但我们有一个原始切片的副本(以及共享的支持数组)。

这样做:

slicesArray[0] = &slice2
fmt.Println((*(slicesArray[0]))[3])

将再次打印25Go Playground

应该保留切片值还是指向切片的指针?

由于切片只是相对较小的描述符,因此您应该保留并使用切片值。切片值已经包含对支持数组的“引用”。通过使用指向切片的指针添加另一个间接只会使事情变得复杂并稍微减慢速度。切片已经被设计成小巧、高效和灵活(与 Go 中的“真实”数组相比)。

当然,指向切片值的指针在某些情况下也可能有用,例如,如果您想创建一个类似于内置函数 append() 的函数而不返回新切片。或者,当您创建一个底层类型为切片类型的自定义类型,并为该类型定义修改切片值的方法时(在这种情况下需要指针接收器)。

进一步阅读:(详细解释所有内容的文档)

Go Slices: usage and internals

Arrays, slices (and strings): The mechanics of 'append'

【讨论】:

  • 那么内置的copy 函数(或:= 运算符,等等)是否使用了COW 方法?我的意思是,内存是否只为保存值为 3 的整数的切片分配一次?
  • 或者,更好的是:这个例子使用的内存量和你的一样吗? play.golang.org/p/e_ELjfseCO
  • @alediaferia 一个数组可以保存元素。切片只是一个描述符,它指的是一个底层数组。在示例中,仅分配了一个后备数组(由此:make([]int, 5))。您可以拥有任意数量的切片引用同一个数组,只有切片描述符会在内存中“复制”。
  • @alediaferia 切片是引用类型。
  • @alediaferia 在您的第二个示例中(链接在您的评论中),也只创建了一个 []int 类型的后备数组,当您这样做时:slicesArray[0] = slice(其中 slicesArray 是type [][]int),切片值,描述符复制到slicesArray的第0个元素。 slice 的后备数组没有被复制或复制,只是复制了切片值。
猜你喜欢
  • 2019-08-27
  • 1970-01-01
  • 2014-07-02
  • 2021-04-29
  • 2019-12-08
  • 1970-01-01
  • 1970-01-01
  • 2013-04-01
  • 1970-01-01
相关资源
最近更新 更多