• 见微知著 带你透过内存看 Slice 和 Array的异同


    hi, 大家好,我是 hhf。

    有这么一个 Go 面试题:请说出 slice 和 array 的区别?

    这简直就是送分题。现在思考一下,你咋样回答才能让面试官满意呢?

    我这里就不贴这道题的答案了。但是我想内存方面简单分析下 slice 和 array 的区别。

    Array

    func main() {
      as := [4]int{10, 5, 8, 7}
      
      fmt.Println("as[0]:", as[0])
      fmt.Println("as[1]:", as[1])
      fmt.Println("as[2]:", as[2])
      fmt.Println("as[3]:", as[3])
    }
    

    这段很简单的代码,声明了一个 array。当然输出结果也足够简单。

    我们现在玩点花活,如何通过非正常的手段访问数组里面的元素呢?在做这个事情之前是需要先知道 array 的底层结构的。其实很简单,Go array 就是一块连续的内存空间。如下图所示

    写一段简单的代码,我们不通过下标访问的方式去获取元素。通过移动指针的方式去获取对应位置的指针。

    func main() {
        as := [4]int{10, 5, 8, 7}
    
        p1 := *(*int)(unsafe.Pointer(&as))
        fmt.Println("as[0]:", p1)
    
        p2 := *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&as)) + unsafe.Sizeof(as[0])))
        fmt.Println("as[1]:", p2)
    
        p3 := *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&as)) + unsafe.Sizeof(as[0])*2))
        fmt.Println("as[2]:", p3)
    
        p4 := *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&as)) + unsafe.Sizeof(as[0])*3))
        fmt.Println("as[3]:", p4)
    }
    

    结果:

    as[0]: 10
    as[1]: 5
    as[2]: 8
    as[3]: 7
    

    下图演示下获取对应位置的值的过程:

    Slice

    同样对于 slice 这段简单的代码:

    func main() {
      as := []int{10, 5, 8, 7}
      
      fmt.Println("as[0]:", as[0])
      fmt.Println("as[1]:", as[1])
      fmt.Println("as[2]:", as[2])
      fmt.Println("as[3]:", as[3])
    }
    

    想要通过移动指针的方式获取 slice 对应位置的值,仍然需要知道 slice 的底层结构。如图:

    func main() {
        as := []int{10, 5, 8, 7}
    
        p := *(*unsafe.Pointer)(unsafe.Pointer(&as))
        fmt.Println("as[0]:", *(*int)(unsafe.Pointer(uintptr(p))))
        fmt.Println("as[1]:", *(*int)(unsafe.Pointer(uintptr(p) + unsafe.Sizeof(&as[0]))))
        fmt.Println("as[2]:", *(*int)(unsafe.Pointer(uintptr(p) + unsafe.Sizeof(&as[0])*2)))
        fmt.Println("as[3]:", *(*int)(unsafe.Pointer(uintptr(p) + unsafe.Sizeof(&as[0])*3)))
    
        var Len = *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&as)) + uintptr(8)))
        fmt.Println("len", Len) 
    
        var Cap = *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&as)) + uintptr(16)))
        fmt.Println("cap", Cap) 
    }
    

    结果:

    as[0]: 10
    as[1]: 5
    as[2]: 8
    as[3]: 7
    len 4
    cap 4
    

    用指针取 slice 的底层 Data 里面的元素跟 array 稍微有点不同:

    • 对 slice 变量 as 取地址后,拿到的是 SiceHeader 的地址,对这个指针进行移动,得到是 slice 的 Data, Len, Cap。
    • 所以当拿到 Data 的值时,我们拿到的是 Data 所指向的 array 的首地址的值。
    • 由于这个值是个指针,需要对这个值 *Data, 取到 array 真正的首地址的指针值
    • 然后对这个值 &(*Data),获取到真正的首地址,然后对这个值进行指针的移动,才能获取到 slice 的数组里的值

    获取 slice cap 和 len:

    获取 slice 的 Data:

    博客文章如无特殊说明,都是作者原创,转载请在醒目的位置链接文章出处及作者,谢谢!
  • 相关阅读:
    iOS 在系统设置中展示Version, Build, Git等信息
    iOS10 App跳转到系统设置
    iOS UIPrintInteractionController打印
    iOS U6b3e转字符串
    Xcode8 报 ”xx“is missing from working copy 的问题解决方法
    OC学习篇之---概述 分类: IOS 2014-11-28 19:06 2349人阅读 评论(1) 收藏
    Android中插件开发篇之----类加载器 分类: Android 2014-11-24 12:15 3275人阅读 评论(4) 收藏
    Mac上安装MySql 分类: JavaWeb 2014-11-10 22:11 446人阅读 评论(0) 收藏
    Andrdoid中对应用程序的行为拦截实现方式之----从Java层进行拦截 分类: Android 2014-11-10 11:23 4311人阅读 评论(12) 收藏
    程序猿的克星 2014-11-07 18:11 4519人阅读 评论(2) 收藏
  • 原文地址:https://www.cnblogs.com/457220157-FTD/p/15179767.html
Copyright © 2020-2023  润新知