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

Written by

in

文章目录
  • 目录
    • 一、Go中的 unsafe 概述
      • 1.1 什么是unsafe?
      • 1.2 使用unsafe的注意事项
      • 1.3 指针转换规则
    • 二、unsafe的核心内容
      • 2.1unsafe.Pointer
      • 2.2uintptr
      • 2.3unsafe.Sizeof、unsafe.Alignof、unsafe.Offsetof
        • 1、unsafe.Sizeof
        • 2、Alignof
        • 3、Offsetof
    • 三、案例分析
      • 3.1 案例 1:使用unsafe修改结构体私有字段
        • 3.2 案例 2:指针运算模拟数组访问
          • 3.3 案例 3:string与[]byte的零拷贝转换

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

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

          • 获取变量的内存地址
          • 直接读写内存
          • 将任意类型转换为 uintptr(指针的整数表示)
          • 访问结构体的私有字段

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

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

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

          • 任何类型的 *T 都可以转换为 unsafe.Pointer;
          • unsafe.Pointer 也可以转换为任何类型的 *T;unsafe.Pointer 可以转换为 uintptr;
          • uintptr 也可以转换为 unsafe.Pointer。

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

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

          • 普通指针不能随意转换类型
          • unsafe.Pointer 可以和 uintptr 互相转换,从而实现指针运算
          var x int = 42
          p := unsafe.Pointer(&x) // &x 是 *int 类型,可以转换为 unsafe.Pointer
          

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

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

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

          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 结构体的内存占用大小,等于它包含的字段类型内存占用大小之和。

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

          func Alignof(x ArbitraryType) uintptr

          • 参数:x 可以是任意类型的表达式(通常传递一个变量或零值)。
          • 返回值:uintptr,表示该类型的对齐系数(单位是字节)。

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

          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
          }
          

          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包内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

          您可能感兴趣的文章:

          • 深入理解Go语言unsafe包
          • golang如何使用指针灵活操作内存及unsafe包原理解析
          • 初探GO中unsafe包的使用
          • Golang unsafe包中的类型和函数详解
          • 深入探索Go语言中unsafe包的使用
          • 使用golang-unsafe包的注意事项及说明
          • GoLang unsafe包详细讲解
          • go中的unsafe包及使用详解

          站内搜索