Go Slice 详解 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
YakuMioto
V2EX    Go 编程语言

Go Slice 详解

  •  
  •   YakuMioto 2018-03-08 21:31:04 +08:00 2356 次点击
    这是一个创建于 2856 天前的主题,其中的信息可能已经有所发展或是发生改变。

    今天被一道题目恶心到了, 发现不研究这些东西可能真的活不下去了, 狠下心来读了一个多小时的源码, 写下些自己对 Slice 的见解吧.

    先说说那个题目.

    // https://play.golang.org/p/2fA3BylTgtf // 请问 s1 和 s2 的值分别是? func main() { s1 := []int{1, 2, 3} s2 := s1[:0] s2 = append(s2, 4) fmt.Println(s1) fmt.Println(s2) } //========== // [4 2 3] // [4] 

    Slice 定义

    先看看 SliceGo 底层的定义

    // https://github.com/golang/go/blob/master/src/reflect/value.go#L1806 type sliceHeader struct { Data unsafe.Pointer // Array pointer Len int // slice length Cap int // slice capacity } 

    原理讲解

    第一行

    s1 := []int{1, 2, 3} 是将 [1, 2, 3] 的首地址 存入了 Data 中, 设置了 Len 为 3, 设置了 Cap 为 3.

    // https://play.golang.org/p/bjP8BKtwQQl // 验证代码. func main() { s1 := []int{1, 2, 3} // 我们可以先将它转成 *reflect.SliceHeader 类型. // *reflect.SliceHeader // 定义: https://github.com/golang/go/blob/master/src/reflect/value.go#L1800 // 顺带着再说一句 uintptr: uintptr 是整型, 可以足够保存指针的值得范围, // 在 32 平台下为 4 字节,在 64 位平台下是 8 字节 sliceHeader1 := (*reflect.SliceHeader)((unsafe.Pointer)(&s1)) fmt.Printf("data address: %#0x, len: %d, cap: %d\n", sliceHeader1.Data, sliceHeader1.Len, sliceHeader1.Cap) } //===== // data address: 0x10414020, len: 3, cap: 3 

    第二行

    s2 := s1[:0] 是将 s1Data 中的值, 赋值给了 s2Data 中, 设置 Len 为 0, s1Cap 赋值给了 s2Cap.

    上面这一段有可能不太好理解, 我直接拿出源码来说.

    // https://github.com/golang/go/blob/master/src/reflect/value.go#1559 func (v Value) Slice(i, j int) Value { // ... 略过无用代码 switch kind := v.kind(); kind { // ... case Slice: typ = (*sliceType)(unsafe.Pointer(v.typ)) s := (*sliceHeader)(v.ptr) base = s.Data cap = s.Cap } // ... // Declare slice so that gc can see the base pointer in it. var x []unsafe.Pointer // Reinterpret as *sliceHeader to edit. s := (*sliceHeader)(unsafe.Pointer(&x)) // 这里是给 s2.Len 进行赋值. s1[:0] i 没有传所以为 0, j 也为 0, 所以 j - i ... s.Len = j - // 这里是给 s2.Cap 进行赋值. cap 在上面的 case 中 被赋值为 3, 3 - 0 emmm... s.Cap = cap - i // if 为真 if cap-i > 0 { // 所以这里是给 s2.Data 进行赋值. // arrayAt 的 4 个参数类型: // p unsafe.Pointer, i int, eltSize uintptr, whySafe string // base 是 s1.Data, i 是 0, eltSize 这个值是根据类型来的, // 在当前例子里是 []int, int 在根据系统, 32 平台下为 4 字节,在 64 位平台下是 8 字节 // 最后一个参数 whySafe 可能是为了做个记录吧... 而且必须说明为啥安全... s.Data = arrayAt(base, i, typ.elem.Size(), "i < cap") } else { // do not advance pointer, to avoid pointing beyond end of slice s.Data = base } } // https://github.com/golang/go/blob/master/src/reflect/value.go#1826 func arrayAt(p unsafe.Pointer, i int, eltSize uintptr, whySafe string) unsafe.Pointer { // 以系统 64 位 为例 // 传的值分别是 s1.Data(0x10414020), 0*8, "i < len" return add(p, uintptr(i)*eltSize, "i < len") } // https://github.com/golang/go/blob/master/src/reflect/type.go#1079 func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer { // 所以这里就相当于 0x10414020+0 return unsafe.Pointer(uintptr(p) + x) } 
    // https://play.golang.org/p/pA6coJh2bSg // 验证代码 func main() { s1 := []int{1, 2, 3} s2 := s1[:0] sliceHeader2 := (*reflect.SliceHeader)((unsafe.Pointer)(&s2)) fmt.Printf("data address: %#0x, len: %d, cap: %d\n", sliceHeader2.Data, sliceHeader2.Len, sliceHeader2.Cap) } //===== // data address: 0x10414020, len: 0, cap: 3 

    可以看见 s1.Datas2.Data 地址都是 0x10414020

    到了这里你可能会问如果地址一样, 为什么 访问 s2[2] 会报错. runtime error: index out of range

    其实猜也能大概猜到, 因为你获取数据的时候 程序是判断了 s2.Len 的.

    代码位置在: https://github.com/golang/go/blob/master/src/reflect/type.go#870 这个函数里面有写.

    结论

    emm.. 不知道....

    11 条回复    2018-03-09 13:38:10 +08:00
    hawken
        1
    hawken  
       2018-03-08 22:40:43 +08:00
    mark
    polythene
        2
    polythene  
       2018-03-08 23:28:52 +08:00
    安利一下这篇文章,里面讲解了你遇到的问题: https://jiajunhuang.com/articles/2017_07_18-golang_slice.md.html
    polythene
        3/div>
    polythene  
       2018-03-08 23:30:20 +08:00
    哦,还有作者也在 V2 @herozem
    herozem
        4
    herozem  
       2018-03-09 00:29:55 +08:00 via iPad
    popbones
        5
    popbones  
       2018-03-09 06:56:07 +08:00
    预料到是这样的结果,没预料到的是并不总是这样的结果(如二楼文章所说的,取决于 append 是否扩容),这有点蛋疼。
    iRiven
        6
    iRiven  
       2018-03-09 09:48:01 +08:00 via Android
    遇到过坑,中间插入

    array = append (append (array [0: index],item), array [index:]...)

    然而这段代码能运行却不能实现中间插入的效果
    lovejoy
        7
    lovejoy  
       2018-03-09 10:04:59 +08:00
    看来还是得老老实实用 copy
    VinllenChen
        8
    VinllenChen  
       2018-03-09 11:02:50 +08:00
    对 slice 的修改是可能会修改原 array 的,需要特别注意。
    YakuMioto
        9
    YakuMioto  
    OP
       2018-03-09 11:14:56 +08:00
    @herozem 哈哈哈, 看了你这篇文章学到了不少东西, 原来 runtime 包里面还有更神奇的东西..
    xrlin
        10
    xrlin  
       2018-03-09 12:52:36 +08:00 via iPhone
    使用 slice 时要时刻提醒自己 slice 只是指向底层数组的一部分,即便是以 slice 做参数,也知识传递一个 slice 的 header 的复制,在涉及添加操作时一定要使用指针。不得不说确实容易导致 bug。
    kirigaya
        11
    kirigaya  
       2018-03-09 13:38:10 +08:00
    一楼
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2545 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 13:51 PVG 21:51 LAX 05:51 JFK 08:51
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86