go基础-unsafe.Pointer和uintptr

809人浏览 / 0人评论

结构定义

unsafe.Pointer

unsafe.Pointer本质是一个int的指针

// src\unsafe\unsafe.go
type ArbitraryType int
type Pointer *ArbitraryType

不同于其他类型,unsafe.Pointer支持4种特殊操作

  • 任意类型的指针均可转换成unsafe.Pointer
  • unsafe.Pointer可转换成任意类型的指针
  • uintptr可转换成unsafe.Pointer
  • unsafe.Pointer可转换成uintptr

uintptr

uintptr是一个整数类型,并且足够大来表示任意地址

// src\builtin\builtin.go
// uintptr is an integer type that is large enough to hold the bit pattern of
// any pointer.
type uintptr uintptr

unsafe.Pointer和uintptr使用场景

类型转换

这种类型转换,需要保证T2的size不大于T1,且具有相同的内存布局;否则,当对T2的读写超过T1的size时,可能会发生错误

// go version go1.14 windows/amd64
package main

import (
	"fmt"
	"unsafe"
)

type UINT64 struct {
	low  uint32
	high uint32
}

func main() {
	data := UINT64{low: 0xf, high: 0xa}
	ptr := unsafe.Pointer(&data) // *UINT64 => unsafe.Pointer
	data2 := *(*uint64)(ptr)     // unsafe.Pointer => *uint64
	fmt.Printf("%x\n", data2)    // 0xa0000000f

	var data32 uint32 = 0xf
	ptr = unsafe.Pointer(&data32)                  // uint32 => unsafe.Pointer
	data64 := *(*uint64)(ptr)                      // unsafe.Pointer => *uint64
	data64 |= (0xa << 32)                          // 修改高32位值
	fmt.Printf("%x,%x\n", data64, 0xf|0xa00000000) // 0x4adfea0000000f,0xa0000000f
}

指针运算

unsafe.pointer是普通指针类型,不能直接进行指针运算;需要先转换成uintptr,运算之后再转换成unsafe.pointer

package main

import (
	"fmt"
	"unsafe"
)

type UINT64 struct {
	low  uint32
	high uint32
}

func main() {
	data := UINT64{low: 0xf, high: 0xa}
	dataAddr := unsafe.Pointer(&data)                                // *UINT64 => unsafe.Pointer
	dataPtr := uintptr(dataAddr)                                     // unsafe.Pointer => uintptr 
	highAddr := unsafe.Pointer(dataPtr + unsafe.Offsetof(data.high)) // 计算data中high字段地址
	*(*uint32)(highAddr) = 0xb                                       // 修改high字段地址存储的值
	data2 := *(*uint64)(dataAddr)                                    // unsafe.Pointer => *uint64
	fmt.Printf("%x\n", data2)                                        // 0xb0000000f
}

uintptr的临时性

uintptr在转换成unsafe.Pointer之前,不能保存在临时变量中;因为uintptr只是一个内存地址的值,当变量因为GC或者其他原因导致地址变动时,uintptr并不会同步修改

package main

import (
	"fmt"
	"time"
	"unsafe"
)

func main() {
	var ptr uintptr
	go func(ptr *uintptr) {
		var data uint64 = 5
		*ptr = uintptr(unsafe.Pointer(&data))               // 获取data的地址
		fmt.Println(*ptr, *(*uint64)(unsafe.Pointer(*ptr))) // 824634023832 5
	}(&ptr)
	time.Sleep(1 * time.Second)                       // 保证data被回收
	fmt.Println(ptr, *(*uint64)(unsafe.Pointer(ptr))) // 824634023832 4437453
}

全部评论