s := []byte("")
fmt.Println(cap(s)) //32
but
s := []byte("")
fmt.Println(cap(s)) //0
fmt.Println(s)
Is the compiler does some optimizations?
s := []byte("")
fmt.Println(cap(s)) //32
but
s := []byte("")
fmt.Println(cap(s)) //0
fmt.Println(s)
Is the compiler does some optimizations?
这个问题很有意思,从结果上来看,编译器肯定是对栈上分配的临时数组变量做了优化(临时分配的固定大小的容量),若一但操作了该数组变量,那么会根据实际使用情况更新数组变量的容量大小。
此外,如果是将该数组定义成包内部变量或者全局变量,这个cap的结果也会是0,因为它会在堆上进行分配。
我也仅仅是根据测试结果进行的猜测,具体内部实现细节可能需要大牛来科普以下。
2 回答2.7k 阅读✓ 已解决
1 回答2.4k 阅读✓ 已解决
2 回答1.8k 阅读✓ 已解决
1 回答1.3k 阅读✓ 已解决
1 回答1.5k 阅读✓ 已解决
2 回答1.4k 阅读
1 回答1.9k 阅读
这个现象确实是编译器优化导致的,我们可以从源码中找出一些证据。
我们把程序反汇编,如上,可以看到对于 s := []byte("") 这样的语句,编译器会为我们生成 stringtoslicebyte函数进行从string到slice的转换。这个函数定义在:
tmpBuf定义为一个长度为32的数组。
当stringtoslicebyte走第一个分支时,从栈上分配内存,如果从栈上分配,就是分配一个长度为32的数组,32是一个写死的和编译器约定好的值。
当stringtoslicebyte走第二个分支时,从堆上分配内存,如果从堆上分配,就是根据字符串的实际长度进行分配。
我们接着搜索编译器的源码,可以找到stringtoslicebyte的生成逻辑。
根据注释看,编译器判断是否在栈上分配的条件,是这个对象是否会逃逸——
编译器会判断一个对象是否会在当前函数外被引用,如果不会就可以通过在当前栈上分配该对象,无需GC处理,达到优化的目的。
这个过程属于编译器逃逸分析(优化)的一部分。逃逸分析的相关源码在:
"cmd/compile/internal/gc/esc.go"
通过如下命令可以获取到编译器逃逸分析的结果
没有fmt.Println(s)时的逃逸分析结果:
有fmt.Println(s)时的逃逸分析结果:
以上,基本上就解释了我们所看到的现象。