【问题标题】:Using Pointers in a for loop在 for 循环中使用指针
【发布时间】:2018-07-27 08:15:54
【问题描述】:

我很难理解为什么我的代码在一种状态下存在错误,而在另一种状态下却没有。我已经有一段时间没有介绍指针了,所以我可能已经生疏了!

基本上我有一个存储库结构,用于在内存中存储一​​个对象,它有一个Store 函数。

type chartsRepository struct {
    mtx    sync.RWMutex
    charts map[ChartName]*Chart
}

func (r *chartsRepository) Store(c *Chart) error {
    r.mtx.Lock()
    defer r.mtx.Unlock()
    r.charts[c.Name] = c
    return nil
}

所以它所做的只是将一个 RW 互斥锁放在上面,并将指针添加到由标识符引用的映射。

然后我有一个函数,它基本上会遍历这些对象的一部分,将它们全部存储在存储库中。

type service struct {
    charts Repository
}

func (svc *service) StoreCharts(arr []Chart) error {
    hasError := false
    for _, chart := range arr {
        err := svc.repo.Store(&chart)
        // ... error handling
    }
    if hasError {
        // ... Deals with the error object
        return me
    }
    return nil
}

上述方法不起作用,起初看起来一切正常,但在稍后尝试访问数据时,映射中的条目都指向同一个 Chart 对象,尽管有不同的键。

如果我执行以下操作并将指针引用移动到另一个函数,一切都会按预期工作:

func (svc *service) StoreCharts(arr []Chart) error {
    // ...
    for _, chart := range arr {
        err := svc.storeChart(chart)
    }
    // ...
}

func (svc *service) storeChart(c Chart) error {
    return svc.charts.Store(&c)
}

我假设问题是因为循环覆盖了for 循环中对chart 的引用,所以指针引用也发生了变化。当指针在独立函数中生成时,该引用永远不会被覆盖。对吗?

我觉得我很愚蠢,但是指针不应该由&chart 生成并且独立于chart 引用吗?我还尝试在for 循环中为指针p := &chart 创建一个新变量,但这也不起作用。

我应该避免在循环中生成指针吗?

【问题讨论】:

    标签: for-loop pointers go mutex


    【解决方案1】:

    这是因为只有一个循环变量chart,并且在每次迭代中只为其分配一个新值。因此,如果您尝试获取循环变量的地址,它将在每次迭代中都相同,因此您将存储相同的指针,并且指向的对象(循环变量)在每次迭代中被覆盖(并且在循环之后它将保留上次迭代中分配的值)。

    Spec: For statements: For statements with range clause:中提到了这个

    迭代变量可以通过“range”子句使用short variable declaration (:=) 的形式声明。在这种情况下,它们的类型被设置为各自迭代值的类型,它们的scope 是“for”语句的块; 它们在每次迭代中重复使用。如果迭代变量是在“for”语句之外声明的,执行后它们的值将是最后一次迭代的值。

    您的第二个版本有效,因为您将循环变量传递给函数,因此将对其进行复制,然后您存储副本的地址(与循环变量分离)。

    你可以在没有函数的情况下达到同样的效果:只需创建一个本地副本并使用它的地址:

    for _, chart := range arr {
        chart2 := chart
        err := svc.repo.Store(&chart2) // Address of the local var
        // ... error handling
    }
    

    另外请注意,您还可以存储切片元素的地址:

    for i := range arr {
        err := svc.repo.Store(&arr[i]) // Address of the slice element
        // ... error handling
    }
    

    这样做的缺点是,由于您存储指向切片元素的指针,所以只要您保留任何指针,切片的整个支持数组就必须保存在内存中(数组不能被垃圾收集) .此外,您存储的指针将与切片共享相同的Chart 值,因此如果有人修改传递的切片的图表值,则会影响您存储其指针的图表。

    查看相关问题:

    Golang: Register multiple routes using range for loop slices/map

    Why do these two for loop variations give me different behavior?

    【讨论】:

    • 谢谢,这是有道理的。我不确定我是否理解为什么循环中的p := &chart 不起作用,是指向chart 的指针而不是指向当时chart 的指针吗? golang 教程上的措辞表明指针是由代码&chart“生成”的。 tour.golang.org/moretypes/1 也许我最好有一个存储库来接受对象本身,而不是指针并总是制作副本以避免潜在的陷阱和垃圾收集问题?
    • @Simon 如果你做p := &chart,那么你再次获取单个循环变量的地址并将其存储在一个变量(指针类型)中,然后在每次迭代中存储这个单个指针.压力是复制chart 值,然后是这个新变量的地址,而不是循环变量。
    • @Simon 在局部变量中复制它并获取其地址并没有错。在 Go 中,获取局部变量的地址是完全正常的,运行时和垃圾收集器会跟踪所有这些,只要你有指针,指向的对象就可以使用。 Go 在这方面不像 C。
    • 是的,我明白这一点,但我认为就存储库应如何与Store 之类的功能一起使用而言,在业务级别上制作副本并保留它们可能更有意义。特别是因为这种内存存储将在某些时候被持久化的东西所取代。谢谢@icza,这真的很有帮助。
    • twitter.com/jawspeak/status/1382842060487626753 这是拼图的形式,play.golang.org/p/Xmn7OWbSnO_k 演示了一些解决方案
    【解决方案2】:

    我今天遇到了类似的问题,创建这个简单的示例帮助我理解了这个问题。

    // Input array of string values
    inputList := []string {"1", "2", "3"}
    // instantiate empty list
    outputList := make([]*string, 0)
    
    for _, value := range inputList {
        // print memory address on each iteration
        fmt.Printf("address of %v: %v\n", value, &value)
        outputList = append(outputList, &value)
    }
    
    // show memory address of all variables
    fmt.Printf("%v", outputList)
    

    打印出来的:

    address of 1: 0xc00008e1e0
    address of 2: 0xc00008e1e0
    address of 3: 0xc00008e1e0
    [0xc00008e1e0 0xc00008e1e0 0xc00008e1e0]
    

    如您所见,value 在每次迭代中的地址始终相同,即使实际值不同(“1”、“2”和“3”)。这是因为 value 被重新分配。

    最后,outputList 中的每个值都指向同一个地址,现在存储值“3”。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-19
      • 1970-01-01
      • 2019-11-15
      • 2020-11-26
      • 1970-01-01
      相关资源
      最近更新 更多