1. 你真的懂 Go Reslice 嗎

        共 3558字,需瀏覽 8分鐘

         ·

        2020-11-21 00:57

        package main

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

        func main() {
        a()
        }

        看到這個(gè)題, 你的第一反應(yīng)是啥?

        (A) 編譯失敗
        (B) panic: runtime error: index out of range [1] with length 1
        (C) []
        (D) 其他

        第一感覺(jué): 肯定能編譯過(guò), 但是運(yùn)行時(shí)一定會(huì) panic 的. 但事與愿違竟然能夠正常運(yùn)行,?結(jié)果是:[]

        疑問(wèn)

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

        a1 和 a2 共享同樣的底層數(shù)組, len(a1) = 1, a1[1] 絕對(duì)會(huì) 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:])

        結(jié)果:

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

        從表面來(lái)看, 從 a[2:] 才開始 panic, 到底是誰(shuí)一手造成這樣的結(jié)果呢?

        匯編上看一目了然

        "".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的數(shù)據(jù)首地址加載到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
        // 省略....

        其實(shí)主要關(guān)心這兩行即可.

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

        是不是很神奇, a[1:] 沒(méi)有調(diào)用runtime.panicSliceB(SB), 而是返回的是一個(gè)空的 slice. 這是為何呢?

        持著懷疑態(tài)度, 去 github 提上一枚 issue.?https://github.com/golang/go/issues/42069



        結(jié)論: 這是故意的, 單純?yōu)榱吮3?reslice 對(duì)稱而已. 這也就解釋了返回一個(gè)空的 slice 的原因.

        reslice 原理

        上面的問(wèn)題已經(jīng)解釋清楚了, 回過(guò)頭來(lái)看正常 reslice 的例子

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

        用簡(jiǎn)單的圖來(lái)描述這段代碼里, a1 和 a2 之間的 reslice 關(guān)系. 可以看到 a1 和 a2 是共享底層數(shù)組的.



        如果你知道這些, 那么 slice 的使用基本上不會(huì)出現(xiàn)問(wèn)題.

        下面這些問(wèn)題你考慮過(guò)嗎 ?

        1. a1, a2 是如何共享底層數(shù)組的?

        2. a1[low:high] 是如何實(shí)現(xiàn)的?

        繼續(xù)來(lái)看這段代碼的匯編:

        "".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 字節(jié), 指向了 a1 的 data 指向的地址空間里

        • 第 5-10 行: 將 [3,4,5,6,7,8] 放入到 a1 的 data 指向的地址空間里

        • 第 11 行: AX 指針后移 16 個(gè)字節(jié). 也就是指向元素 5 的位置

        • 第 12 行: 將 SP 指針下移 32 字節(jié)指向即將返回的 slice (其實(shí)就是 a2 ), 同時(shí)將 AX 放入到 SP. 注意 AX 放入 SP 里的是一個(gè)指針, 也就造成了 a1, a2 是共享同一塊內(nèi)存空間的

        • 第 13 行: 將 SP 指針下移 40 字節(jié)指向了 a2 的 len, 同時(shí) 把 4 放入到 SP, 也就是 len(a2) = 4

        • 第 14 行: 將 SP 指針下移 48 字節(jié)指向了 a2 的 cap, 同時(shí) 把 4 放入到 SP, 也就是 cap(a2) = 4

        下圖是 slice 的 棧圖, 可以配合著上面的匯編一塊看.



        看到這里是不是一目了然了. 于是有了下面的這些結(jié)論:

        1. reslice 完全是利用匯編實(shí)現(xiàn)的

        2. reslice 時(shí), slice 的 data 通過(guò)指針的移動(dòng)完成, 造成了共享相同的底層數(shù)據(jù), 同時(shí)將新的 len, cap 放入對(duì)應(yīng)的位置

        至此, golang reslice 的原理基本已經(jīng)闡述清楚了.

        參考資料

        1. 深入 Go 的底層,帶你走近一群有追求的人

        2. 匯編角度看 Slice,一個(gè)新的世界

        3. Why slice not painc

        4. Slice expressions

        5. A Quick Guide to Go's Assembler

        6. plan9 assembly 完全解析

        原文鏈接:?https://www.haohongfan.com/post/2020-10-20-golang-slice/


        瀏覽 41
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 久草福利资源网 | 国产精品无码一区二区三区免费 | 日皮视频在线观看 | 男人鸡鸡捅女人 | 五月天黄色激情网站 |