Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go 非类型安全unsafe包

Go中非类型安全unsafe包的详细使用

作者:数据知道

Go语言中的unsafe包是一个特殊工具,允许绕过类型系统直接操作内存,具有高性能优势但风险极高,本文就来详细的介绍一下unsafe包的使用,感兴趣的可以了解一下

一、Go中的 unsafe 概述

1.1 什么是unsafe?

Go 语言中的 unsafe 包是一个既强大又危险的工具,它允许我们绕过 Go 的类型系统,直接操作内存。虽然它在某些高性能场景下非常有用,但使用不当也会导致程序崩溃或安全漏洞。

unsafe 是 Go 语言中的一个特殊包,它提供了一些可以绕过 Go 类型安全机制的机制。通过 unsafe,你可以:

⚠️ 注意:使用 unsafe 会破坏 Go 的类型安全和内存安全,应谨慎使用,并尽量避免在生产代码中滥用。

1.2 使用unsafe的注意事项

  1. 不保证兼容性unsafe 的实现可能随 Go 版本变化,代码可能在新版本中失效。
  2. GC 无法追踪 uintptr:如果将 uintptr 转换为 unsafe.Pointer 后没有立即使用,可能会被 GC 回收,导致非法访问。
  3. 类型安全被破坏:可能导致内存损坏、数据竞争或程序崩溃。
  4. 可读性差:滥用 unsafe 会让代码难以理解和维护。

1.3 指针转换规则

Go 语言中存在三种类型的指针,它们分别是:常用的 *T、unsafe.Pointer 及 uintptr。可以总结出这三者的转换规则:

可以发现,unsafe.Pointer 主要用于指针类型的转换,而且是各个指针类型转换的桥梁。

二、unsafe的核心内容

2.1unsafe.Pointer

unsafe.Pointer 是一种特殊的指针类型,它可以指向任意类型的数据。它和普通指针(如 *int)之间的主要区别是:

var x int = 42
p := unsafe.Pointer(&x) // &x 是 *int 类型,可以转换为 unsafe.Pointer

2.2uintptr

uintptr 是一个整数类型,足够大以存储任意指针的值。它常用于指针运算,例如:

p := unsafe.Pointer(&x)
ptr := uintptr(p) // 转换为 uintptr
ptr += 8          // 指针运算
p = unsafe.Pointer(ptr) // 再转回 unsafe.Pointer

⚠️ 注意uintptr 不是指针,它不会被 GC 追踪,因此不能长时间持有。

2.3unsafe.Sizeof、unsafe.Alignof、unsafe.Offsetof

1、unsafe.Sizeof

Sizeof:返回类型或变量的大小(字节)。Sizeof 函数可以返回一个类型所占用的内存大小,这个大小只与类型有关,和类型对应的变量存储的内容大小无关,比如 bool 型占用一个字节、int8 也占用一个字节。

通过 Sizeof 函数你可以查看任何类型(比如字符串、切片、整型)占用的内存大小,示例代码如下:

fmt.Println(unsafe.Sizeof(true))
fmt.Println(unsafe.Sizeof(int8(0)))
fmt.Println(unsafe.Sizeof(int16(10)))
fmt.Println(unsafe.Sizeof(int32(10000000)))
fmt.Println(unsafe.Sizeof(int64(10000000000000)))
fmt.Println(unsafe.Sizeof(int(10000000000000000)))
fmt.Println(unsafe.Sizeof(string("数据知道")))
fmt.Println(unsafe.Sizeof([]string{"数据u知道","张三"}))

对于整型来说,占用的字节数意味着这个类型存储数字范围的大小,比如 int8 占用一个字节,也就是 8bit,所以它可以存储的大小范围是 -128~~127,也就是 −2^(n-1) 到 2^(n-1)−1。其中 n 表示 bit,int8 表示 8bit,int16 表示 16bit,以此类推。

小提示:一个 struct 结构体的内存占用大小,等于它包含的字段类型内存占用大小之和。

2、Alignof

Alignof:是 Go 语言 unsafe 包中的一个函数,用于返回某个类型的对齐系数(alignment),即该类型的变量在内存中存放时的起始地址必须是其对齐系数的整数倍。

func Alignof(x ArbitraryType) uintptr

3、Offsetof

Offsetof:返回结构体字段相对于结构体起始地址的偏移量

三、案例分析

3.1 案例 1:使用unsafe修改结构体私有字段

3.2 案例 2:指针运算模拟数组访问

package main
import (
	"fmt"
	"unsafe"
)
func main() {
	arr := [3]int{10, 20, 30}
	// 获取数组首地址
	basePtr := unsafe.Pointer(&arr[0])
	// 模拟指针运算访问第二个元素
	secondPtr := (*int)(unsafe.Pointer(uintptr(basePtr) + unsafe.Sizeof(arr[0])))
	fmt.Println(*secondPtr) // 输出 20
}

3.3 案例 3:string与[]byte的零拷贝转换

package main
import (
	"fmt"
	"unsafe"
)
func main() {
	s := "hello, unsafe"
	// 获取 string 的底层结构
	strHeader := (*struct {
		data uintptr
		len  int
	})(unsafe.Pointer(&s))
	// 构造 []byte 的底层结构
	bytes := *(*[]byte)(unsafe.Pointer(&struct {
		data uintptr
		len  int
		cap  int
	}{
		data: strHeader.data,
		len:  strHeader.len,
		cap:  strHeader.len,
	}))
	fmt.Println(string(bytes)) // 输出 "hello, unsafe"
}

总结:unsafe 是 Go 语言中的一把“双刃剑”。unsafe 包里的功能虽然不安全,但的确很香,比如指针运算、类型转换等,都可以帮助我们提高性能。不过还是建议尽可能地不使用,因为它可以绕开 Go 语言编译器的检查,可能会因为你的操作失误而出现问题。当然如果是需要提高性能的必要操作,还是可以使用,比如 []byte 转 string,就可以通过 unsafe.Pointer 实现零内存拷贝,

到此这篇关于Go中非类型安全unsafe包的详细使用的文章就介绍到这了,更多相关Go 非类型安全unsafe包内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文