乐趣区

关于golang:Golang学习笔记unsafePointer和uintptr

如果你看 go 的源码,尤其是 runtime 的局部的源码,你肯定常常会发现 unsafe.Pointer 和 uintptr 这两个函数,例如上面就是 runtime 外面的 map 源码实现外面的一个函数:

func (b *bmap) overflow(t *maptype) *bmap {return *(**bmap)(add(unsafe.Pointer(b), uintptr(t.bucketsize)-sys.PtrSize))
}

那么这两个办法有什么用呢?上面咱们来重点介绍一下。

Go 中的指针及与指针对指针的操作次要有以下三种:

一一般的指针类型,例如 var intptr *T,定义一个 T 类型指针变量。

二内置类型 uintptr,实质是一个无符号的整型,它的长度是跟平台相干的,它的长度能够用来保留一个指针地址。

三是 unsafe 包提供的 Pointer,示意能够指向任意类型的指针。

1. 一般的指针类型

count := 1
Counter(&count)
fmt.Println(count)

func Counter(count *int) {*count++}

一般指针能够通过援用来批改变量的值,这个跟 C 语言指针有点像。

2.uintptr 类型

uintptr 用来进行指针计算,因为它是整型,所以很容易计算出下一个指针所指向的地位。uintptr 在 builtin 包中定义,定义如下:

// uintptr is an integer type that is large enough to hold the bit pattern of any pointer.
// uintptr 是一个能足够包容指针位数大小的整数类型
type uintptr uintptr

尽管 uintpr 保留了一个指针地址,但它只是一个值,不援用任何对象。因而应用的时候要留神以下状况:

1. 如果 uintptr 地址相关联对象挪动,则其值也不会更新。例如 goroutine 的堆栈信息发生变化

2.uintptr 地址关联的对象能够被垃圾回收。GC 不认为 uintptr 是活援用,因而 unitptr 地址指向的对象能够被垃圾收集。

一个 uintptr 能够被转换成 unsafe.Pointer, 同时 unsafe.Pointer 也能够被转换为 uintptr。能够应用应用 uintptr + offset 计算出地址,而后应用 unsafe.Pointer 进行转换,格局如下:p = unsafe.Pointer(uintptr(p) + offset)

n := 10

b := make([]int, n)
for i:= 0;i< n;i++ {b[i] = i
}
fmt.Println(b)
// [0 1 2 3 4 5 6 7 8 9]

// 取 slice 的最初的一个元素
end := unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + 9 * unsafe.Sizeof(b[0]))
// 等价于 unsafe.Pointer(&b[9])
fmt.Println(*(*int)(end))
// 9

3.unsafe.Pointer

unsafe.Pointer 是特地定义的一种指针类型, 它能够蕴含任意类型变量的地址。Pointer 在 unsafe 包中定义,定义如下:

package unsafe


// ArbitraryType is here for the purposes of documentation only and is not actually
// part of the unsafe package. It represents the type of an arbitrary Go expression.
// ArbitraryType 在这里不是 unsafe 包的理论的一部分,仅仅是为了文档记录
type ArbitraryType int


type Pointer *ArbitraryType


func Sizeof(x ArbitraryType) uintptr

func Offsetof(x ArbitraryType) uintptr

func Alignof(x ArbitraryType) uintptr

官网文档 unsafe.Pointer 的应用有以下阐明:

Pointer represents a pointer to an arbitrary type. There are four special operations
available for type Pointer that are not available for other types:    //  Pointer 代表了一个任意类型的指针。Pointer 类型有四种非凡的操作是其余类型不能应用的:
   - A pointer value of any type can be converted to a Pointer.       //  任意类型的指针能够被转换为 Pointer
   - A Pointer can be converted to a pointer value of any type.       //  Pointer 能够被转换为工作类型的值的指针
   - A uintptr can be converted to a Pointer.                         //  uintptr 能够被转换为 Pointer
   - A Pointer can be converted to a uintptr.                         //  Pointer 能够被转换为 uintptr
Pointer therefore allows a program to defeat the type system and read and write
arbitrary memory. It should be used with extreme care.                //  因而 Pointer 容许程序不按类型零碎的要求来读写任意的内存,应该十分小心地应用它。

所以 unsafe.Pointer 做的次要是用来进行桥接,用于不同类型的指针进行相互转换。

在任何状况下,后果都必须持续指向原调配的对象。

4.unsafe.Pointer,uintptr 与一般指针的相互转换

  • unsafe.Pointer 和一般指针的互相转换

    var f float64 = 1.0
    fmt.Println(Float64bits(f))
    // 4607182418800017408
    
    func Float64bits(f float64) uint64 {return *((*uint64)(unsafe.Pointer(&f)))
    }
    

    借助 unsafe.Pointer 指针,实现 float64 转换为 uint64 类型。当然,咱们不能够间接通过 *p 来获取 unsafe.Pointer 指针指向的实在变量的值,因为咱们并不知道变量的具体类型。另外一个重要的要留神的是,在进行一般类型转换的时候,要留神转换的前后的类型要有雷同的内存布局,上面两个构造也能实现转换,就因为他们有雷同的内存布局

    type s1 struct {
        id int
        name string
    }
    
    type s2 struct {field1 *[5]byte
        filed2 int
    }
    
    b := s1{name:"123"}
    var j s2
    j = *(*s2)(unsafe.Pointer(&b))
    fmt.Println(j)
    
  • unsafe.Pointer 和 uintrptr 的相互转换及配合

uintptr 类型的次要是用来与 unsafe.Pointer 配合应用来拜访和操作 unsafe 的内存。unsafe.Pointer 不能执行算术操作。要想对指针进行算术运算必须这样来做:

1. 将 unsafe.Pointer 转换为 uintptr

2. 对 uintptr 执行算术运算

3. 将 uintptr 转换回 unsafe.Pointer, 而后拜访 uintptr 地址指向的对象

须要小心的是,下面的步骤对于垃圾收集器来说应该是原子的,否则可能会导致问题。

例如,在第 1 步之后,援用的对象可能被收集。如果在步骤 3 之后产生这种状况,指针将是一个有效的 Go 指针,并可能导致程序解体

unsafe.Pointer 和 uintrptr 的转换

package main

import (
    "fmt"
    "unsafe"
)

type Person struct {
    age int
    name string
}
func main() {p := &Person{age: 30, name: "Bob"}
    
   // 获取到 struct s 中 b 字段的地址
    p := unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Offsetof(p.name))
    
    // 将其转换为一个 string 的指针,并且打印该指针的对应的值
    fmt.Println(*(*string)(p))
}
退出移动版