设为首页 加入收藏

TOP

深度解密Go语言之unsafe(二)
2019-06-03 10:07:56 】 浏览:297
Tags:深度 解密 语言 unsafe
类型的指针和 unsafe.Pointer 可以相互转换。
  • uintptr 类型和 unsafe.Pointer 可以相互转换。
  • type pointer uintptr

    pointer 不能直接进行数学运算,但可以把它转换成 uintptr,对 uintptr 类型进行数学运算,再转换成 pointer 类型。

    // uintptr 是一个整数类型,它足够大,可以存储
    type uintptr uintptr

    还有一点要注意的是,uintptr 并没有指针的语义,意思就是 uintptr 所指向的对象会被 gc 无情地回收。而 unsafe.Pointer 有指针语义,可以保护它所指向的对象在“有用”的时候不会被垃圾回收。

    unsafe 包中的几个函数都是在编译期间执行完毕,毕竟,编译器对内存分配这些操作“了然于胸”。在 /usr/local/go/src/cmd/compile/internal/gc/unsafe.go 路径下,可以看到编译期间 Go 对 unsafe 包中函数的处理。

    更深层的原理需要去研究编译器的源码,这里就不去深究了。我们重点关注它的用法,接着往下看。

    unsafe 如何使用

    获取 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
    }

    Len,cap 的转换流程如下:

    Len: &s => pointer => uintptr => poiter => *int => int
    Cap: &s => pointer => uintptr => poiter => *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 进行转换,得到 hamp 字段的值,只不过,现在 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

    map 源码中的应用

    在 map 源码中,mapaccess1、mapassign、mapdelete 函数中,需要定位 key 的位置,会先对 key 做哈希运算。

    例如:

    b := (*bmap)(unsafe.Pointer(uintptr(h.buckets) + (hash&m)*uintptr(t.bucketsize)))

    h.buckets 是一个 unsafe.Pointer,将它转换成 uintptr,然后加上 (hash&m)*uintptr(t.bucketsize),二者相加的结果再次转换成 unsafe.Pointer,最后,转换成 bmap 指针,得到 key 所落入的 bucket 位置。如果不熟悉这个公式,可以看看上一篇文章,浅显易懂。

    上面举的例子相对简单,来看一个关于赋值的更难一点的例子:

    // store new key/value at insert position
    if t.indirectkey {
        kmem := newobject(t.key)
        *(*unsafe.Pointer)(insertk) = kmem
        insertk = kmem
    }
    if t.indirectvalue {
        vmem := newobject(t.elem)
        *(*unsafe.Pointer)(val) = vmem
    }
    
    typedmemmove(t.key, insertk, key)

    这段代码是在找到了 key 要插入的位置后,进行“赋值”操作。insertk 和 val 分别表示 key 和 value 所要“放置”的地址。如果 t.indirectkey 为真,说明 bucket 中存储的是 key 的指针,因此需要将 insertk 看成指针的指针,这样才能将 bucket 中的相应位置的值设置成指向真实 key 的地址值,也就是说 key 存放的是指针。

    下面这张图展示了设置 key 的全部操作:

    map assign

    obj 是真实的 key 存放的地方。第 4 号图,obj 表示执行完 typedmemmove 函数后,被成功赋值。

    Offsetof 获取成员偏移量

    对于一个结构体,通过 offset 函数可以获取结构体成员的偏移量,进而获取成员的地址,读写该地址的内存,就可以达到改变成员值的目的。

    这里有一个内存分配相关的事实:结构体会被分配一块连续的内存,结构体的地址也代表了第一个成员的地址。

    我们来看一个例子:

    package main
    
    import (
        "fmt"
        "unsafe"
    )
    
    type Programmer struct {
        name string
        language string
    }
    
    func main() {
        p := Programmer{"stefno", "go"}
        fmt.Println(p)
        
        name := (*string)(unsafe.Pointer(&p))
        *name = "qcrao"
    
        lang := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Offsetof(p.language))
    首页 上一页 1 2 3 下一页 尾页 2/3/3
    】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
    上一篇高并发 多线程批量ping工具 nbpin.. 下一篇红黑树原理详解及golang实现

    最新文章

    热门文章

    Hot 文章

    Python

    C 语言

    C++基础

    大数据基础

    linux编程基础

    C/C++面试题目