Golang unsafe

297 阅读2分钟

个人笔记记录

是什么unsafe

Package unsafe contains operations that step around the type safety of Go programs.

为什么会有unsafe

go的指针是类型安全的,所以有时我们需要绕过这个限制来达到实现某个功能的目的

unsafe提供的方法

type ArbitraryType int
type Pointer *ArbitraryType

ArbitraryType表示一个任意的Go表达式的类型,所以可以把Pointer当作C语言里的void* 以下是unsafe包提供的三个函数

func Sizeof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr

上面三个函数都在编译期间运行,所以都可以复制给const常量

package main

import (
	"fmt"
	"unsafe"
)

type Num struct {
	i string
	j int
}

// invalid: const initializer unsafe.Pointer(&Num{}) is not a constant
// const pointer = unsafe.Pointer(&Num{})

const sizeOf = unsafe.Sizeof(Num{})
const offsetOf = unsafe.Offsetof(Num{}.i)
const alignOf = unsafe.Alignof(Num{})

func main() {
	fmt.Println(sizeOf)
	fmt.Println(offsetOf)
	fmt.Println(alignOf)
}
// 24
// 0
// 8

unsafe的pointer只支持如下四种操作,我们都是根据这四种操作来实现我们的需求的

A pointer value of any type can be converted to a Pointer.
A Pointer can be converted to a pointer value of any type.
A uintptr can be converted to a Pointer.
A Pointer can be converted to a uintptr.

image.png Pointer不能进行地址偏移运算,需要转成uintptr,进行运算后再转回Pointer,并且uintptr仅仅是一个整数,和指针以及指向的对象没任何关系,所以不要把uintptr保存下来留到以后使用,因为对象可能被gc移动或回收,但是uintptr的值并不会被自动更新。而unsafe.Pointer 有指针语义,可以保护它所指向的对象在“有用”的时候不会被垃圾回收.

unsafe使用

获取StringHeader的Len值

StringHeader结构如下

type StringHeader struct {
	Data uintptr 
	Len  int //通过unsafe.Offsetof拿到Len的偏移为8
}

s := "12345"
fmt.Println((*reflect.StringHeader)(unsafe.Pointer(&s)).Len)   // 5
fmt.Println(*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(8)))) // 5

获取slice信息

sl := make([]int, 5, 10)
slLen := *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&sl)) + uintptr(8)))
slCap := *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&sl)) + uintptr(16)))
fmt.Println("slLen: ", slLen)  // 5
fmt.Println("slCap: ", slCap)  // 10

使用offset

package main

import (
	"fmt"
	"unsafe"
)


type Person struct {
	name string
	age  int
}

func main() {
	p := Person{}
	fmt.Println(p)   // { 0}
	namePtr := (*string)(unsafe.Pointer(&p))
	*namePtr = "a"

	agePtr := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Offsetof(p.age)))
	*agePtr = 100
	fmt.Println(p)  // {a 100}
}

string 和 slice 的相互转换

type StringHeader struct {
    Data uintptr
    Len  int
}

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}
package main

import (
	"fmt"
	"reflect"
	"unsafe"
)


func main() {
	sli := []byte{'a', 'b', 'c', '1', '2', '3', '4'}
	fmt.Println(slice2string(sli))   // abc1234

	str := "qwert"
	fmt.Println(string2slice(str))   // [113 119 101 114 116]
}

func slice2string(b []byte) string {
	sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b))

	strH := reflect.StringHeader{
		Data: sliceHeader.Data,
		Len:  sliceHeader.Len,
	}
	return *(*string)(unsafe.Pointer(&strH))
}

func string2slice(str string) []byte {
	strHeader := (*reflect.StringHeader)(unsafe.Pointer(&str))

	sliH := reflect.SliceHeader{
		Data: strHeader.Data,
		Len:  strHeader.Len,
		Cap:  strHeader.Len,
	}
	return *(*[]byte)(unsafe.Pointer(&sliH))
}

修改未导出字段

├── main.go
└── testpa
    └── testpa.go

test.go

package test

type Test struct {
	name string
	age  int
}

var T = Test{
	name: "old",
	age: 0,
}

main.go

package main

import (
	"fmt"
	"unsafe"
	"example/testpa"
)

func main() {
	val := testpa.T
	fmt.Println(val)  // {old 0}
	*(*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&val)))) = "new"
	*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&val)) + uintptr(16))) = 10
	fmt.Println(val)  // {new 10}
}

参考: zhuanlan.zhihu.com/p/67852800