package mainfunc a() []int {    a1 := []int{3}    a2 := a1[1:]    return a2}func main() {    a()}

看到这个题, 你的第一反馈是啥?

(A) 编译失败(B) panic: runtime error: index out of range [1] with length 1(C) [](D) 其余

第一感觉: 必定能编译过, 然而运行时肯定会panic的. 但大失所望居然可能失常运行, 后果是:[]

疑难

a1 := []int{3}a2 := a1[1:]fmt.Println("a[1:]", a2)

a1 和 a2 共享同样的底层数组, len(a1) = 1, a1[1]相对会panic, 然而a[1:]却能失常输入, 这是为何?

从外表动手

整体上看下整体的状况

a1 := []int{3}fmt.Printf("len:%d, cap:%d", len(a1), cap(a1))fmt.Println("a[0:]", a1[0:])fmt.Println("a[1:]", a1[1:])fmt.Println("a[2:]", a1[2:])

后果:

len:1, cap:1a[0:]: [1]a[1:] []panic: runtime error: slice bounds out of range [2:1]

从外表来看, 从a[2:]才开始panic, 到底是谁一手造成这样的后果呢?

汇编上看高深莫测

"".a STEXT size=87 args=0x18 locals=0x18    // 省略...    0x0028 00040 (main.go:6)    CALL    runtime.newobject(SB)    0x002d 00045 (main.go:6)    MOVQ    8(SP), AX  // 将slice的数据首地址加载到AX寄存器    0x0032 00050 (main.go:6)    MOVQ    $3, (AX)   // 把3放入到AX寄存器中, 也就是a1[0]    0x0039 00057 (main.go:8)    MOVQ    AX, "".~r0+32(SP)    0x003e 00062 (main.go:8)    XORPS    X0, X0     // 初始化 X0 寄存器    0x0041 00065 (main.go:8)    MOVUPS    X0, "".~r0+40(SP) // 将X0放入返回值    0x0046 00070 (main.go:8)    MOVQ    16(SP), BP    0x004b 00075 (main.go:8)    ADDQ    $24, SP    0x004f 00079 (main.go:8)    RET    // 省略....

其实次要关怀这两行即可.

0x003e 00062 (main.go:8)    XORPS    X0, X0     // 初始化 X0 寄存器0x0041 00065 (main.go:8)    MOVUPS    X0, "".~r0+40(SP) // 将X0放入返回值

是不是很神奇, a[1:] 没有调用runtime.panicSliceB(SB), 而是返回的是一个空的slice. 这是为何呢?

持着狐疑态度, 去 github 提上一枚 issue. https://github.com/golang/go/...

论断: 这是成心的, 单纯为了放弃reslice对称而已. 这也就解释了返回一个空的slice的起因.

reslice 原理

下面的问题曾经解释分明了, 回过头来看失常 reslice 的例子

func a() []int {    a1 := []int{3, 4, 5, 6, 7, 8}    a2 := a1[2:]    return a2}

用简略的图来形容这段代码里, a1 和 a2 之间的reslice 关系. 能够看到 a1 和 a2 是共享底层数组的.

如果你晓得这些, 那么 slice 的应用基本上不会呈现问题.

上面这些问题你思考过吗 ?

  1. a1, a2 是如何共享底层数组的?
  2. a1[low:high]是如何实现的?

持续来看这段代码的汇编:

"".a STEXT size=117 args=0x18 locals=0x18    // 省略...    0x0028 00040 (main.go:4)    CALL    runtime.newobject(SB)    0x002d 00045 (main.go:4)    MOVQ    8(SP), AX    0x0032 00050 (main.go:4)    MOVQ    $3, (AX)    0x0039 00057 (main.go:4)    MOVQ    $4, 8(AX)    0x0041 00065 (main.go:4)    MOVQ    $5, 16(AX)    0x0049 00073 (main.go:4)    MOVQ    $6, 24(AX)    0x0051 00081 (main.go:4)    MOVQ    $7, 32(AX)    0x0059 00089 (main.go:4)    MOVQ    $8, 40(AX)    0x0061 00097 (main.go:5)    ADDQ    $16, AX    0x0065 00101 (main.go:6)    MOVQ    AX, "".~r0+32(SP)    0x006a 00106 (main.go:6)    MOVQ    $4, "".~r0+40(SP)    0x0073 00115 (main.go:6)    MOVQ    $4, "".~r0+48(SP)    0x007c 00124 (main.go:6)    MOVQ    16(SP), BP    0x0081 00129 (main.go:6)    ADDQ    $24, SP    0x0085 00133 (main.go:6)    RET    // 省略....
  • 第4行: 将 AX 栈顶指针下移 8 字节, 指向了 a1 的 data 指向的地址空间里
  • 第5-10行: 将 [3,4,5,6,7,8] 放入到 a1 的 data 指向的地址空间里
  • 第11行: AX 指针后移 16 个字节. 也就是指向元素 5 的地位
  • 第12行: 将 SP 指针下移 32 字节指向行将返回的 slice (其实就是 a2 ), 同时将 AX 放入到 SP. 留神 AX 放入 SP 里的是一个指针, 也就造成了a1, a2是共享同一块内存空间的
  • 第13行: 将 SP 指针下移 40 字节指向了 a2 的 len, 同时 把 4 放入到 SP, 也就是 len(a2) = 4
  • 第14行: 将 SP 指针下移 48 字节指向了 a2 的 cap, 同时 把 4 放入到 SP, 也就是 cap(a2) = 4

下图是 slice 的 栈图, 能够配合着下面的汇编一块看.

看到这里是不是高深莫测了. 于是有了上面的这些论断:

  1. reslice 齐全是利用汇编实现的
  2. reslice 时, slice 的 data 通过指针的挪动实现, 造成了共享雷同的底层数据, 同时将新的 len, cap 放入对应的地位

至此, golang reslice的原理根本曾经论述分明了.

参考资料

  1. 深刻Go的底层,带你走近一群有谋求的人
  2. 汇编角度看 Slice,一个新的世界
  3. Why slice not painc
  4. Slice expressions
  5. A Quick Guide to Go's Assembler
  6. plan9 assembly 齐全解析