2 回答
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 导致编译器之间或什至不同编译器目标之间一致的容量
- 2 回答
- 0 关注
- 123 浏览
添加回答
举报