# 我擦～字符串转字节切片后，切片的容量竟然千奇百怪

## 神奇的现象

### 现象一

```a := "abc"
bs := []byte(a)
fmt.Println(bs, len(bs), cap(bs))
// 输出： [97 98 99] 3 8```

### 现象二

```a := "abc"
bs := []byte(a)
fmt.Println(len(bs), cap(bs))
// 输出: 3 32```

### 现象三

```bs := []byte("abc")
fmt.Println(len(bs), cap(bs))
// 输出: 3 3```

### 现象四

```a := ""
bs := []byte(a)
fmt.Println(bs, len(bs), cap(bs))
// 输出: [] 0 0```

### 现象五

```a := ""
bs := []byte(a)
fmt.Println(len(bs), cap(bs))
// 输出: 0 32```

## 分析

### 字符串变量转切片

```"".main STEXT size=495 args=0x0 locals=0xd8
0x0000 00000 (test.go:5)    TEXT    "".main(SB), ABIInternal, \$216-0
0x0000 00000 (test.go:5)    MOVQ    (TLS), CX
0x0009 00009 (test.go:5)    LEAQ    -88(SP), AX
0x000e 00014 (test.go:5)    CMPQ    AX, 16(CX)
0x0012 00018 (test.go:5)    JLS    485
0x0018 00024 (test.go:5)    SUBQ    \$216, SP
0x001f 00031 (test.go:5)    MOVQ    BP, 208(SP)
0x0027 00039 (test.go:5)    LEAQ    208(SP), BP
0x002f 00047 (test.go:5)    FUNCDATA    \$0, gclocals·7be4bbacbfdb05fb3044e36c22b41e8b(SB)
0x002f 00047 (test.go:5)    FUNCDATA    \$1, gclocals·648d0b72bb9d7f59fbfdbee57a078eee(SB)
0x002f 00047 (test.go:5)    FUNCDATA    \$2, gclocals·2dfddcc7190380b1ae77e69d81f0a101(SB)
0x002f 00047 (test.go:5)    FUNCDATA    \$3, "".main.stkobj(SB)
0x002f 00047 (test.go:6)    PCDATA    \$0, \$1
0x002f 00047 (test.go:6)    PCDATA    \$1, \$0
0x002f 00047 (test.go:6)    LEAQ    go.string."abc"(SB), AX
0x0036 00054 (test.go:6)    MOVQ    AX, "".a+96(SP)
0x003b 00059 (test.go:6)    MOVQ    \$3, "".a+104(SP)
0x0044 00068 (test.go:7)    MOVQ    \$0, (SP)
0x004c 00076 (test.go:7)    PCDATA    \$0, \$0
0x004c 00076 (test.go:7)    MOVQ    AX, 8(SP)
0x0051 00081 (test.go:7)    MOVQ    \$3, 16(SP)
0x005a 00090 (test.go:7)    CALL    runtime.stringtoslicebyte(SB)
0x005f 00095 (test.go:7)    MOVQ    40(SP), AX
0x0064 00100 (test.go:7)    MOVQ    32(SP), CX
0x0069 00105 (test.go:7)    PCDATA    \$0, \$2
0x0069 00105 (test.go:7)    MOVQ    24(SP), DX
0x006e 00110 (test.go:7)    PCDATA    \$0, \$0
0x006e 00110 (test.go:7)    PCDATA    \$1, \$1
0x006e 00110 (test.go:7)    MOVQ    DX, "".bs+112(SP)
0x0073 00115 (test.go:7)    MOVQ    CX, "".bs+120(SP)
0x0078 00120 (test.go:7)    MOVQ    AX, "".bs+128(SP)```

```"".main STEXT size=393 args=0x0 locals=0xe0
0x0000 00000 (test.go:5)    TEXT    "".main(SB), ABIInternal, \$224-0
0x0000 00000 (test.go:5)    MOVQ    (TLS), CX
0x0009 00009 (test.go:5)    LEAQ    -96(SP), AX
0x000e 00014 (test.go:5)    CMPQ    AX, 16(CX)
0x0012 00018 (test.go:5)    JLS    383
0x0018 00024 (test.go:5)    SUBQ    \$224, SP
0x001f 00031 (test.go:5)    MOVQ    BP, 216(SP)
0x0027 00039 (test.go:5)    LEAQ    216(SP), BP
0x002f 00047 (test.go:5)    FUNCDATA    \$0, gclocals·0ce64bbc7cfa5ef04d41c861de81a3d7(SB)
0x002f 00047 (test.go:5)    FUNCDATA    \$1, gclocals·00590b99cfcd6d71bbbc6e05cb4f8bf8(SB)
0x002f 00047 (test.go:5)    FUNCDATA    \$2, gclocals·8dcadbff7c52509cfe2d26e4d7d24689(SB)
0x002f 00047 (test.go:5)    FUNCDATA    \$3, "".main.stkobj(SB)
0x002f 00047 (test.go:6)    PCDATA    \$0, \$1
0x002f 00047 (test.go:6)    PCDATA    \$1, \$0
0x002f 00047 (test.go:6)    LEAQ    go.string."abc"(SB), AX
0x0036 00054 (test.go:6)    MOVQ    AX, "".a+120(SP)
0x003b 00059 (test.go:6)    MOVQ    \$3, "".a+128(SP)
0x0047 00071 (test.go:7)    PCDATA    \$0, \$2
0x0047 00071 (test.go:7)    LEAQ    ""..autotmp_5+64(SP), CX
0x004c 00076 (test.go:7)    PCDATA    \$0, \$1
0x004c 00076 (test.go:7)    MOVQ    CX, (SP)
0x0050 00080 (test.go:7)    PCDATA    \$0, \$0
0x0050 00080 (test.go:7)    MOVQ    AX, 8(SP)
0x0055 00085 (test.go:7)    MOVQ    \$3, 16(SP)
0x005e 00094 (test.go:7)    CALL    runtime.stringtoslicebyte(SB)
0x0063 00099 (test.go:7)    MOVQ    40(SP), AX
0x0068 00104 (test.go:7)    MOVQ    32(SP), CX
0x006d 00109 (test.go:7)    PCDATA    \$0, \$3
0x006d 00109 (test.go:7)    MOVQ    24(SP), DX
0x0072 00114 (test.go:7)    PCDATA    \$0, \$0
0x0072 00114 (test.go:7)    PCDATA    \$1, \$1
0x0072 00114 (test.go:7)    MOVQ    DX, "".bs+136(SP)
0x007a 00122 (test.go:7)    MOVQ    CX, "".bs+144(SP)
0x0082 00130 (test.go:7)    MOVQ    AX, "".bs+152(SP)```

`func stringtoslicebyte(buf *tmpBuf, s string) []byte`

```// 现象一给runtime.stringtoslicebyte的传参
0x002f 00047 (test.go:6)    LEAQ    go.string."abc"(SB), AX // 将字符串"abc"放入寄存器AX
0x0036 00054 (test.go:6)    MOVQ    AX, "".a+96(SP) // 将AX中的内容存入变量a中
0x003b 00059 (test.go:6)    MOVQ    \$3, "".a+104(SP) // 将字符串长度3存入变量a中
0x0044 00068 (test.go:7)    MOVQ    \$0, (SP) // 将0 传递个runtime.stringtoslicebyte(SB)的第一个参数(笔者猜测对应go中的nil)
0x004c 00076 (test.go:7)    PCDATA    \$0, \$0 // 据说和gc有关， 具体还不清楚， 一般情况可以忽略
0x004c 00076 (test.go:7)    MOVQ    AX, 8(SP) // 将AX中的内容传递给runtime.stringtoslicebyte(SB)的第二个参数
0x0051 00081 (test.go:7)    MOVQ    \$3, 16(SP) // 将字符串长度传递给runtime.stringtoslicebyte(SB)的第二个参数
0x005a 00090 (test.go:7)    CALL    runtime.stringtoslicebyte(SB) // 调用函数， 此行后面的几行代码是将返回值赋值给变量bs
// 现象二给runtime.stringtoslicebyte的传参
0x002f 00047 (test.go:6)    LEAQ    go.string."abc"(SB), AX // 将字符串"abc"放入寄存器AX
0x0036 00054 (test.go:6)    MOVQ    AX, "".a+120(SP) // 将AX中的内容存入变量a中
0x003b 00059 (test.go:6)    MOVQ    \$3, "".a+128(SP) // 将字符串长度3存入变量a中
0x0047 00071 (test.go:7)    PCDATA    \$0, \$2
0x0047 00071 (test.go:7)    LEAQ    ""..autotmp_5+64(SP), CX // 将内部变量autotmp_5放入寄存器CX
0x004c 00076 (test.go:7)    PCDATA    \$0, \$1
0x004c 00076 (test.go:7)    MOVQ    CX, (SP) // 将CX中的内容传递给runtime.stringtoslicebyte(SB)的第一个参数
0x0050 00080 (test.go:7)    PCDATA    \$0, \$0
0x0050 00080 (test.go:7)    MOVQ    AX, 8(SP) // 将AX中的内容传递给runtime.stringtoslicebyte(SB)的第二个参数
0x0055 00085 (test.go:7)    MOVQ    \$3, 16(SP) // 将字符串长度传递给runtime.stringtoslicebyte(SB)的第二个参数
0x005e 00094 (test.go:7)    CALL    runtime.stringtoslicebyte(SB)```

```func stringtoslicebyte(buf *tmpBuf, s string) []byte {
var b []byte
if buf != nil && len(s) <= len(buf) {
*buf = tmpBuf{}
b = buf[:len(s)]
} else {
b = rawbyteslice(len(s))
}
copy(b, s)
return b
}```

```# 在go源码根目录执行下面的命令
grep stringtoslicebyte -r . | grep -v "//"```

```// A Node is a single node in the syntax tree.
// Actually the syntax tree is a syntax DAG, because there is only one
// node with Op=ONAME for a given instance of a variable x.
// The same is true for Op=OTYPE and Op=OLITERAL. See Node.mayBeShared.```

```// 此代码位于cmd/compile/internal/gc/esc.go中
const (
// ...
EscNone           // Does not escape to heap, result, or parameters.
...
)```

```# 执行变量逃逸分析命令: go run -gcflags '-m -l' test.go
# 现象一逃逸分析如下：
./test.go:7:14: ([]byte)(a) escapes to heap
./test.go:8:13: main ... argument does not escape
./test.go:8:13: bs escapes to heap
./test.go:8:21: len(bs) escapes to heap
./test.go:8:30: cap(bs) escapes to heap
[97 98 99] 3 8
# 现象二逃逸分析如下：
./test.go:7:14: main ([]byte)(a) does not escape
./test.go:8:13: main ... argument does not escape
./test.go:8:17: len(bs) escapes to heap
./test.go:8:26: cap(bs) escapes to heap
3 32```

```const tmpStringBufSize = 32
type tmpBuf [tmpStringBufSize]byte```

```func rawbyteslice(size int) (b []byte) {
cap := roundupsize(uintptr(size))
p := mallocgc(cap, nil, false)
if cap != uintptr(size) {
}
*(*slice)(unsafe.Pointer(&b)) = slice{p, size, int(cap)}
return
}```

```func roundupsize(size uintptr) uintptr {
if size < _MaxSmallSize {
if size <= smallSizeMax-8 {
return uintptr(class_to_size[size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]])
} else {
return uintptr(class_to_size[size_to_class128[(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv]])
}
}
if size+_PageSize < size {
return size
}
return round(size, _PageSize)
}```

### 字符串直接转切片

```types.NewArray
```

## 结论

#### 字符串转字节切片步骤如下

1. 判断是否是常量， 如果是常量则转换为等容量等长的字节切片
2. 如果是变量， 先判断生成的切片是否发生变量逃逸

• 如果逃逸或者字符串长度>32， 则根据字符串长度可以计算出不同的容量
• 如果未逃逸且字符串长度<=32, 则字符切片容量为32

## 扩展

#### 常见逃逸情况

1. 函数返回局部指针
2. 栈空间不足逃逸
3. 动态类型逃逸, 很多函数参数为interface类型，比如fmt.Println(a …interface{})，编译期间很难确定其参数的具体类型, 也会发生逃逸
4. 闭包引用对象逃逸