使用unsafe.Pointer获取slice和map的长度

Lambda0828 · 2023-04-20 09:43:04 · 1746 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2023-04-20 09:43:04 的主题,其中的信息可能已经有所发展或是发生改变。

获取 slice 长度

通过对slice的学习,我们知道了 slice header 的结构体定义:

    // runtime/slice.go
type slice struct {
    array unsafe.Pointer // 元素指针
    len   int // 长度 
    cap   int // 容量
}

调用 make 函数新建一个 slice,底层调用的是 makeslice 函数,返回的是 slice 结构体: func makeslice(et *_type, len, cap int) slice 因此我们可以通过 unsafe.Pointer 和 uintptr 进行转换,得到 slice 的字段值。

    func main() {
        s := make([]int, 9, 20)
        var Len = *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(8)))  
        fmt.Println(Len, len(s)) // 9 9
    var Cap = *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(16)))
        fmt.Println(Cap, cap(s)) // 20 20
}

上文采用加uintptr(8)和uintptr(16),是因为底层slice结构连续的,切片变量 s 在内存中占用了连续的 3 个机器字。在 64 位架构下,一个机器字的大小为 8 个字节,因此 s 的第一个机器字是指向底层数组的指针,第二个机器字是切片的长度,第三个机器字是切片的容量。可能有些小伙伴不了解字的含义,这里和大家解释一下:

16位系统: 1个机器字---2个字节--16位bit
32位系统: 1个机器字---4个字节--32位bit
64位系统: 1个及其子---8个字节--64位bit
其中:字节是内存操作的基本单位,所以uintptr(8)才能计算偏移位置。

slice内存结构如下:

+--------------+
|   Ptr        |  -- 指向底层数组的指针
+--------------+
|   Len        |  -- 切片的长度
+--------------+
|   Cap        |  -- 切片的容量
+--------------+

Len,cap 的转换流程如下:

Len: &s => pointer => uintptr => pointer => *int => int
Cap: &s => pointer => uintptr => pointer => *int => int

获取 map 长度

再来看一下 map:

type hmap struct {
        count     int
        flags     uint8
        B         uint8
        noverflow uint16
        hash0     uint32
    buckets    unsafe.Pointer
        oldbuckets unsafe.Pointer
        nevacuate  uintptr
    extra *mapextra
}

和 slice 不同的是,makemap 函数返回的是 hmap 的指针,注意是指针: func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap 我们依然能通过 unsafe.Pointer 和 uintptr 进行转换,得到 hmap 字段的值,只不过,现在 count 变成二级指针了:

func main() {
        mp := make(map[string]int)
        mp["qcrao"] = 100
        mp["stefno"] = 18
    count := **(**int)(unsafe.Pointer(&mp))
        fmt.Println(count, len(mp)) // 2 2
}

count 的转换过程:

&mp => pointer => **int => int

注意:上面的转化,是因为hmap第一个成员是count,所以可以将hmap指针转化为int指针。转化后获取的就是count地址,然后进行(**int)取值就可以获取数量了。


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1746 次点击  
加入收藏 微博
1 回复  |  直到 2023-04-22 11:29:46
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传