The previous article talked about slicing. The magical problem I encountered today is still related to slicing. What is the specific magic method? Let’s take a look at the following phenomena
1 2 3 4 |
|
1 2 3 4 |
|
1 2 3 |
|
1 2 3 4 |
|
1 2 3 4 |
|
I am full here There are question marks in my mind
一个小小的字符串转切片, 内部究竟发生了什么, 竟然如此的神奇。这种时候只好祭出前一篇文章的套路了, 看看汇编代码(希望之后有机会能够对go的汇编语法进行简单的介绍
)有没有什么关键词能够帮助我们
以下为现象一转换的汇编代码关键部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
以下为现象二转换的汇编代码关键部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
在看汇编代码之前, 我们首先来看一看runtime.stringtoslicebyte
的函数签名
1 |
|
到这里只靠关键词已经无法看出更多的信息了,还是需要稍微了解一下汇编的语法,笔者在这里列出一点简单的分析, 之后我们还是可以通过取巧的方法发现更多的东西
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
通过上面汇编代码的分析可以知道,现象一和现象二的区别就是传递给runtime.stringtoslicebyte
的第一个参数不同。通过对runtime包中stringtoslicebyte
函数分析,第一个参数是否有值和字符串长度会影响代码执行的分支,从而生成不同的切片, 因此容量不一样也是常理之中, 下面我们看源码
1 2 3 4 5 6 7 8 9 10 11 |
|
然而, stringtoslicebyte的第一个参数什么情况下才会有值,什么情况下为nil, 我们仍然不清楚。那怎么办呢, 只好祭出全局搜索大法:
1 2 |
|
最终在go的编译器源码cmd/compile/internal/gc/walk.go发现了如下代码块
我们查看mkcall
函数签名可以知道, 从第四个参数开始的所有变量都会作为参数传递给第一个参数对应的函数, 最后生成一个*Node
的变量。其中Node结构体解释如下:
1 2 3 4 |
|
综合上述信息我们得出的结论是,编译器会对stringtoslicebyte的函数调用生成一个AST(抽象语法树)对应的节点。因此我们也知道传递给stringtoslicebyte函数的第一个变量也就对应于上图中的变量a.
其中a的初始值为nodnil()
的返回值,即默认为nil
. 但是n.Esc == EscNone
时,a会变成一个数组。我们看一下EscNone的解释.
1 2 3 4 5 6 |
|
由上可知, EscNone
用来判断变量是否逃逸,到这儿了我们就很好办了,接下来我们对现象一和现象二的代码进行逃逸分析.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
根据上面的信息我们知道在现象一中,bs变量发生了逃逸,现象二中变量未发生逃逸,也就是说stringtoslicebyte函数的第一个参数在变量未发生逃逸时其值不为nil,变量发生逃逸时其值为nil。到这里我们已经搞明白stringtoslicebyte的第一个参数了, 那我们继续分析stringtoslicebyte的内部逻辑
我们在runtime/string.go中看到stringtoslicebyte第一个参数的类型定义如下:
1 2 3 |
|
综上: 现象二中bs变量未发生变量逃逸, stringtoslicebyte第一个参数不为空且是一个长度为32的byte数组, 因此在现象二中生成了一个容量为32的切片
根据对stringtoslicebyte的源码分析, 我们知道现象一调用了rawbyteslice
函数
1 2 3 4 5 6 7 8 9 10 |
|
由上面的代码知道, 切片的容量通过runtime/msize.go中的roundupsize
函数计算得出, 其中_MaxSmallSize和class_to_size均定义在runtime/sizeclasses.go
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
由于字符串abc的长度小于_MaxSmallSize(32768),故切片的长度只能取数组class_to_size中的值, 即0, 8, 16, 32, 48, 64, 80, 96, 112, 128....
s
至此, 现象一中切片容量为什么为8也真相大白了。相信到这里很多人已经明白现象四和现象五是怎么回事儿了, 其逻辑分别与现象一和现象二是一致的, 有兴趣的, 可以在自己的电脑上面试一试。
那你说了这么多, 现象三还是不能解释啊。请各位看官莫急, 接下来我们继续分析。
相信各位细心的小伙伴应该早就发现了我们在上面的cmd/compile/internal/gc/walk.go
源码图中折叠了部分代码, 现在我们就将这块神秘的代码赤裸裸的展示出来
We analyzed this code and found that the go compiler is divided into three steps when converting strings to byte slices
to generate AST.
First determine whether the variable is a constant string. If it is a constant string, directly create an array with the same length as the string through types.NewArray
The slice variable generated by the constant string must also be subjected to escape analysis and determine whether its size is greater than the maximum length allowed by the function stack to be allocated to the variable, thereby determining whether the node is allocated on the stack or in On the heap
Finally, if the string length is greater than 0, copy the string content into the byte slice and then return. Therefore, it is completely clear that the slicing capacity in Phenomenon 3 is 3
The steps for string to byte slicing are as follows
Determine whether it is a constant. If it is a constant, convert it into a byte slice of equal capacity and length.
If it is a variable, first determine whether the generated slice is A variable escape occurs
If the escape or the string length is >32, different capacities can be calculated based on the string length
If there is no escape and the string length is
Common escape situations
The above is the detailed content of Can you tell me the capacity of string to byte slice in Go?. For more information, please follow other related articles on the PHP Chinese website!