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

 更新时间:2025年09月26日 09:57:04   作者:数据知道  
Go语言中的unsafe包是一个特殊工具,允许绕过类型系统直接操作内存,具有高性能优势但风险极高,本文就来详细的介绍一下unsafe包的使用,感兴趣的可以了解一下

一、Go中的 unsafe 概述

1.1 什么是unsafe?

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

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

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

⚠️ 注意:使用 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。可以总结出这三者的转换规则:

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

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

二、unsafe的核心内容

2.1unsafe.Pointer

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

  • 普通指针不能随意转换类型
  • unsafe.Pointer 可以和 uintptr 互相转换,从而实现指针运算
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

  • 参数:x 可以是任意类型的表达式(通常传递一个变量或零值)。
  • 返回值: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包内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • GoFrame gredis配置文件及配置方法对比

    GoFrame gredis配置文件及配置方法对比

    这篇文章主要为大家介绍了GoFrame gredis配置管理中,配置文件及配置方法对比,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • 详解Go 语言中的比较操作符

    详解Go 语言中的比较操作符

    这篇文章专注于 6 个操作符,==,!=,<,<=,> 和 >=。我们将深入探讨它们的语法和用法的细微差别,感兴趣的朋友跟随脚本之家小编一起看看吧
    2018-08-08
  • go zero微服务框架logx日志组件剖析

    go zero微服务框架logx日志组件剖析

    这篇文章主要为大家介绍了go zero微服务框架logx日志组件剖析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • Go实现用户每日限额的方法(例一天只能领三次福利)

    Go实现用户每日限额的方法(例一天只能领三次福利)

    这篇文章主要介绍了Go实现用户每日限额的方法(例一天只能领三次福利)
    2022-01-01
  • 图文详解Go程序如何编译并运行起来的

    图文详解Go程序如何编译并运行起来的

    Go语言这两年在语言排行榜上的上升势头非常猛,Go语言虽然是静态编译型语言,但是它却拥有脚本化的语法,下面这篇文章主要给大家介绍了关于Go程序如何编译并运行起来的相关资料,需要的朋友可以参考下
    2024-05-05
  • go语言通过反射获取和设置结构体字段值的方法

    go语言通过反射获取和设置结构体字段值的方法

    这篇文章主要介绍了go语言通过反射获取和设置结构体字段值的方法,实例分析了Go语言反射的使用技巧,需要的朋友可以参考下
    2015-03-03
  • Golang小数操作指南之判断小数点位数与四舍五入

    Golang小数操作指南之判断小数点位数与四舍五入

    这篇文章主要给大家介绍了关于Golang小数操作指南之判断小数点位数与四舍五入的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2022-03-03
  • 通过案例简单聊聊为什么说Go中的字符串是不能被修改的

    通过案例简单聊聊为什么说Go中的字符串是不能被修改的

    在接触Go这么语言,可能你经常会听到这样一句话,对于字符串不能修改,可能你很纳闷,日常开发中我们对字符串进行修改也是很正常的,为什么又说Go中的字符串不能进行修改呢,本文就来通过实际案例给大家演示,为什么Go中的字符串不能进行修改
    2023-07-07
  • golang中的jwt使用教程流程分析

    golang中的jwt使用教程流程分析

    这篇文章主要介绍了golang中的jwt使用教程,接下来我们需要讲解一下Claims该结构体存储了token字符串的超时时间等信息以及在解析时的Token校验工作,需要的朋友可以参考下
    2023-05-05
  • GoFrame基于性能测试得知grpool使用场景

    GoFrame基于性能测试得知grpool使用场景

    这篇文章主要为大家介绍了GoFrame基于性能测试得知grpool使用场景示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06

最新评论