调用Println会改变切片容量?寻求合理技术解释
为啥调用带
...interface{}的函数后,切片容量会变? 先看你贴的这段代码,运行起来的结果确实有点反直觉:调用fmt.Println(x)之后,x的容量居然和新创建的[]byte("a")不一样了,而没被任何函数调用过的y就完全正常。我来给你拆解一下背后的原因:
先搞懂字符串转切片的底层逻辑
当你写[]byte("a")的时候,Go编译器并不会新分配一块内存存这个字节,而是直接复用字符串常量"a"的底层只读数组——毕竟字符串是不可变的,这么做能省内存。这时候生成的切片长度和容量都是1,而且它指向的是一块只读内存区域。
为啥传给...interface{}函数就出问题了?
当你把这个指向只读内存的切片传给fmt.Println这类带...interface{}参数的函数时,Go的运行时会做个检查:哎,这个切片的底层数组是只读的?不行,因为interface{}里的值理论上是可以被修改的(虽然fmt.Println不会改,但运行时得保证通用性)。
于是运行时就会悄悄做一件事:把原来的只读数组复制到一块可写的内存里,同时把切片的容量调整成Go默认的小切片扩容大小(通常是16)。而且这里有个特殊点:这个操作会直接修改原切片的底层数组引用和容量——别奇怪,这是早期Go版本里runtime处理这类情况的特殊逻辑,虽然看起来有点违背值传递的直觉,但确实是当时的实现方式。
验证一下就清楚了
给代码加几行打印容量的语句,就能看到明显变化:
package main import ( "fmt" ) func main() { x := []byte("a") fmt.Println("调用fmt.Println前,x的容量:", cap(x)) // 输出 1 fmt.Println(x) fmt.Println("调用fmt.Println后,x的容量:", cap(x)) // 输出 16 fmt.Println(cap(x) == cap([]byte("a"))) // 自然是false y := []byte("a") fmt.Println("y的容量:", cap(y)) // 输出 1 fmt.Println(cap(y) == cap([]byte("a"))) // true }
总结一下
只要是接受...interface{}的函数,处理指向只读内存的切片时,都会触发这个隐式的拷贝和容量调整。但如果你的切片是用make创建的(底层数组本身就是可写的),就不会出现这个问题。
内容的提问来源于stack exchange,提问作者vitr
相关产品推荐
相关产品推荐

