为了账号安全,请及时绑定邮箱和手机立即绑定

是使用引用还是复制指向切片的指针?

是使用引用还是复制指向切片的指针?

Go
海绵宝宝撒 2021-09-10 15:31:21
我发现我的代码有不同的结果,如下所示,这是一个指向 Go.Tour 编译器(http://tour.golang.org/welcome/1)和我的本地编译器(Go 版本 1.4)之间的幻灯片的指针哪一个是正确的?而且我还想知道我的代码 p1、p2 之间的指针是如何工作的?因为地址似乎没有移动,但 p1 使用引用,而 p2 使用复制。package mainimport "fmt"func main() {    var a []int    var b []int    a = append(a, 0)    b = append(b, 0)    p := &a[0]    fmt.Printf("a[0] = %d pointer=%d, p = %d \n", a[0], &a[0], *p)    a[0] = 2    fmt.Printf("a[0] = %d pointer=%d, p = %d \n", a[0], &a[0], *p)    /*        a[0] = 0, p = 0        a[0] = 2, p = 2    */    var c []int    var d []int    c = append(c, 0)    d = append(d, 0)    p2 := &c[0]    fmt.Printf("c[0]=%d pointer=%d, p2 = %d\n", c[0], &c[0], *p2)    c = append(c, 1)    c[0] = 2    fmt.Printf("c[0]=%d pointer=%d, p2 = %d\n", c[0], &c[0], *p2)    /*         c[0]=0, p2 = 0        c[0]=2, p2 = 0      copy the same code run in http://tour.golang.org/welcome/1 will get.        c[0]=0, p2 = 0        c[0]=2, p2 = *2*  << why??    */}更新:我使用指向切片的指针的原因是我试图测试在 Go 的 Web 端存在 RUST 的向量 push_pack 问题。请参阅http://doc.rust-lang.org/nightly/intro.html#ownership。
查看完整描述

2 回答

?
RISEBY

TA贡献1856条经验 获得超5个赞

首先,对于任何带有切片的内容,我建议您阅读Go Slices: usage and internals。简短的故事是 Go 使用 append 处理切片的容量可能很不稳定。


给定的切片变量具有三个组成部分:指向数据数组的底层指针、长度和容量。已经有很多关于差异的说法,但这里的重要部分是长度是(有效)当前使用的底层内存缓冲区的部分,容量是底层缓冲区的整体大小。这是一个不精确的定义,但它在实践中效果很好。


神秘的下一部分是append内置函数。append 的功能有时实际上有点难以推理,这可能是 Go 中最大的问题之一:


如果底层缓冲区足够大(cap > len),只需将 len 增加要添加的元素数并将数据放入新空间。

如果底层缓冲区不够大,则分配一个更大容量的新缓冲区,将旧缓冲区复制到新缓冲区中,然后添加所有新元素。

2 的最大症结在于,在追加之后对同一个切片进行两次任意操作,很难知道旧的或新的内存缓冲区是否被先验地影响。确实,让我们试试这个:


var c []int

var d []int

c = append(c, 0)

d = append(d, 0)

p2 := &c[0]

fmt.Printf("c[0]=%d pointer=%d, p2 = %d\n", c[0], &c[0], *p2)

c = append(c, 1)

c[0] = 2

fmt.Printf("c[0]=%d pointer=%d, p2 = %d\n", c[0], &c[0], *p2)

c = append(c, 1)

c[0] = 25

fmt.Printf("c[0]=%d pointer=%d, p2 = %d\n", c[0], &c[0], *p2)



你会得到c[0]=25, p2=2。我们只添加了一个语句,突然间指针和切片值发散了!


这意味着上限发生了变化,或者更确切地说,使用了新的缓冲区。实际上,cap(c)在第一个 append 之后但在第三个之前打印,将产生2. 这意味着当将单个元素附加到容量为 0 的切片时,Go 会初始化 [footnote] 长度为 1 和容量为2的切片。所以在第二次追加之后没有分配新的缓冲区,因为有空间。这就是为什么p2和c[0]在第二次追加之后是一样的,但在第三次之后是一样的。


一般来说,虽然切片和对内存中特定位置的引用的确切规则是一致的,但在实践中切片增长的行为非常挑剔,通常最好永远不要依赖指向切片值(或两个切片变量)的指针具有相同的底层缓冲区),除非您计划从不使用 append,或者将缓冲区预先分配make到使用 append 永远不会重新分配的大小。


[脚注] 不完全正确,我想给出一个巨大的警告,即 append 后的确切容量取决于实现。请不要依赖 APPEND 导致编译器之间或什至不同编译器目标之间一致的容量


查看完整回答
反对 回复 2021-09-10
  • 2 回答
  • 0 关注
  • 123 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信