当前位置: 首页 > news >正文

golang -- unsafe 包

unsafe 包

Go 语言中的 unsafe 包是一个非常强大且危险的工具,它允许开发者进行低级的内存操作,直接访问内存地址。通常,Go 是一种安全的语言,它会自动管理内存并做类型检查,确保程序的安全性和稳定性。但是,unsafe 包允许开发者绕过 Go 的类型安全检查,直接操作内存。因为它提供了对底层内存的直接访问,所以在使用时需要非常小心。


Sizeof

func Sizeof(x ArbitraryType) uintptr  //ArbitraryType 代表任意类型

在 Go 语言中,unsafe 包提供了一个函数 unsafe.Sizeof,可以用来获取变量或类型的大小(以字节为单位)。

示例:

	a := 1b := []int{1, 2, 3}c := "abc"fmt.Println(unsafe.Sizeof(a)) // 8fmt.Println(unsafe.Sizeof(b)) // 24fmt.Println(unsafe.Sizeof(c)) // 16

需要注意的是,unsafe.Sizeof 仅计算变量在内存中的占用大小,不包括动态分配的内存(如切片或映射的底层数据)。

静态内存 VS 动态内存

  1. 静态内存占用:指的是类型本身在内存中的固定占用大小。例如,int 类型占 8 字节(在 64 位系统中),struct 的字段的大小,以及 map 或 slice 这种类型的结构体本身占用的内存大小。

  2. 动态内存占用:指的是 切片、映射、字符串 等类型在实际使用过程中,可能会从堆上分配更多内存来存储它们的数据。unsafe.Sizeof 仅计算静态内存占用,不会包括这些动态分配的内存区域。


示例:

  1. 切片
s := []int{1, 2, 3, 4, 5}fmt.Println(unsafe.Sizeof(s)) // 24

解释:

切片本身是一个结构体,通常它有三个字段:

  • 一个指向底层数组的指针(8 字节,64 位系统)

  • 当前切片的长度(8 字节,64 位系统)

  • 当前切片的容量(8 字节,64 位系统)

所以,unsafe.Sizeof(s) 输出 24 字节,并没有把底层数组的内存大小包括在内。

如果切片有 5 个整数(每个整数占 8 字节),那么底层数组的内存是 5 * 8 = 40 字节,但这并不会被 unsafe.Sizeof 计算进去。

  1. 字符串
	str := "hello"fmt.Println(unsafe.Sizeof(str)) //16

解释:

字符串 str 的底层内存是由两个部分组成的:

  • 一个指向底层字节数组的指针(8 字节,64 位系统)

  • 字符串的长度(8 字节,64 位系统)

unsafe.Sizeof(str) 会返回 16 字节(在 64 位系统上),这只是字符串结构体的大小,不包括底层字节数组(即 “hello” 存储的数据)的内存


Offsetof

func Offsetof(x ArbitraryType) uintptr

unsafe.Offsetof ,用于获取结构体内某个字段相对于结构体起始地址的偏移量(或者说返回值就是结构体地址起始处到字段地址起始处两者之间的字节数),所以 x 必须是一个结构体的字段。它返回一个 uintptr 类型的值,表示该字段在结构体中的内存偏移位置

示例:

	type person struct {name stringage  int}p := person{name: "aa",age:  11,}fmt.Println(unsafe.Sizeof(p))        //24fmt.Println(unsafe.Offsetof(p.name)) //0fmt.Println(unsafe.Sizeof(p.name))   //16fmt.Println(unsafe.Offsetof(p.age))  //16fmt.Println(unsafe.Sizeof(p.age))    //8

求结构体占用字节数涉及到内存对齐相关知识,可以看🔗结构体中内存对齐部分


Alignof

func Alignof(x ArbitraryType) uintptr

unsafe.Alignof ,用于获取类型或变量对齐数的函数,以字节为单位,取计算机字长(对于64位机器是8)与变量占用字节数的较小值

示例:

	fmt.Println(unsafe.Alignof([]int{1}))      //slice -- 8fmt.Println(unsafe.Alignof('a'))           //byte -- 1fmt.Println(unsafe.Alignof(complex(1, 2))) //complex -- 8fmt.Println(unsafe.Alignof(0))             //int -- 8fmt.Println(unsafe.Alignof("abc"))         //string -- 8fmt.Println(unsafe.Alignof(float64(0)))    //float64 -- 8fmt.Println(unsafe.Alignof(person{}))      //person -- 8

Pointer

type Pointer *ArbitraryType

unsafe.Pointer 类型是一个通用的指针类型,可以指向任何数据类型。

unsafe.Pointer 本身不能直接解引用,因此需要通过类型转换将其转换为其他具体类型的指针。
为什么不能解引用?
在 Go 中,普通的指针(例如 * int, * string 等)都指向某个特定类型的数据,可以通过解引用操作符 * 来访问指针指向的值。然而,unsafe.Pointer 是一个通用指针,不直接指向特定类型的数据,而Go 需要明确知道它指向的数据类型才能安全地进行解引用。所以不能像普通的指针那样通过解引用获取其值,或者直接对它进行取地址操作。

unsafe.Pointer 类型的几种特殊操作:

  1. 任何类型的指针都可以转换为 unsafe.Pointer

    实现两种类型 *T1、*T2 的转换可以通过 unsafe.Pointer 这一中间量来完成

    (1)首先将 *T1 转换成 unsafe.Pointer
    (2)再将 unsafe.Pointer 转换成 *T2

    示例:

func main() {fmt.Println(Float64bits(12.3))fmt.Println(Float64frombits(Float64bits(12.3)))
}func Float64bits(f float64) uint64 {return *(*uint64)(unsafe.Pointer(&f)) //float64->*float64->unsafe.Pointer->*uint64->uint64
}func Float64frombits(b uint64) float64 {return *(*float64)(unsafe.Pointer(&b))//unit64->*unit64->unsafe.Pointer->*float64->float64
}

   *T1 和 *T2 的转换有一定要求和规则:

  (1)内存布局相同:为了能够安全地将一种类型转换为另一种类型,必须满足这两种类型的内存布局一致。如果是结构体类型,包括:

  • 字段顺序相同

  • 字段大小相同

  • 对齐要求相同

  (2) 相同的底层类型:当我们说两种类型的底层类型相同时,指的是它们的数据存储方式必须一致。例如:

   基本类型:比如两个都是 int32 类型,或者两个都是 float64 类型,底层存储是相同的。

   结构体:结构体类型的内存布局必须完全相同,比如字段的顺序、类型、大小和对齐方式一致。

   数组:如果两个数组的类型相同(比如都是 [10]int),它们的内存布局也是相同的。

  (3) 不同底层类型不能转换:对于类型不相同的情况(如 int 转 string),即使它们在某些情况下有相似的字节表示(比如在底层都有字节存储),它们的表示和存储方式也不同,因此不能直接通过内存转换进行类型转换。例如:

   int 和 string:虽然 string 内部也使用字节存储数据,但它不仅存储字节数据,还有指向数据的指针和长度信息,和 int 的内存布局完全不同,因此不能直接转换。

   int 和 float64:虽然这两者都是数字类型,但它们的二进制表示(内存布局)完全不同,因此也不能直接转换。

  1. 将unsafe.Pointer转换为uintptr

    unsafe.Pointer 是指针类型
    uintptr 不是指针类型,而是一个 无符号整数类型。它可以用来表示 指针的内存地址,但它并不具备指针的特性。简单来说,uintptr 存储的是指针的地址(即整数值),只是一个整数,不具备解引用功能,也不直接表示指向某个数据的引用

    示例:

	num := 1fmt.Println(unsafe.Pointer(&num))                 //0xc0000940a8fmt.Printf("0x%x", uintptr(unsafe.Pointer(&num))) //0xc0000940a8

Add

func Add(ptr Pointer, len IntegerType) Pointer

unsafe.Add 是 Go 语言中 unsafe 包提供的一个函数,允许对指针进行偏移操作。具体来说,unsafe.Add 的作用是根据偏移量来更新指针地址,这样可以方便地访问数组、切片或者结构体中的元素。

注意:偏移量是以字节为单位的

示例:

	s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}ps := unsafe.Pointer(&s[0])fmt.Println(*(*int)(unsafe.Add(ps, 8)))  //2fmt.Println(*(*int)(unsafe.Add(ps, 16))) //3

解释:

unsafe.Pointer 函数
ps 通过 unsafe.Pointer 获取了指向切片第一个元素的指针,指向 s[0]

unsafe.Add 函数
通过 Add 函数,从 ps 开始,偏移量为 8 个字节,int 类型是 8 个字节,也就是向后移动一个元素,指向 2 ,再将类型转换成 *int,最后解引用


SliceData

func SliceData(slice []ArbitraryType) *ArbitraryTyp

SliceData, 获取的是切片的底层数组的指针,而不是切片本身。对于切片来说,底层数组保存了数据的实际内存地址,SliceData 返回的是这个地址。可以使用这个地址来进行内存操作或与其他低级 API 进行交互

示例

func main() {nums := []int{1, 2, 3, 4}for p, i := unsafe.Pointer(&nums[0]), 0; i < len(nums); p, i = unsafe.Add(p, unsafe.Sizeof(nums[0])), i+1 {num := *(*int)(p)fmt.Println(num)}
}

解释:
unsafe.Pointer 函数
将 nums[0](切片的第一个元素)的地址强制转换为 unsafe.Pointer 类型。这样 p 就指向了 nums 切片的第一个元素 nums[0]。

i := 0:i 是索引,初始化为 0,用于跟踪遍历到的切片位置。

unsafe.Add 函数
在 p 的基础上增加 unsafe.Sizeof(nums[0]) 字节,实际是将指针 p 向后移动 4 字节,指向下一个 int 元素

i + 1:每次循环结束后,i 加 1,用于跟踪切片中的索引位置。


Slice

func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType

Slice ,将指针和长度转换为一个切片

示例:

func main() {nums := []int{1, 2, 3, 4}numsRef1 := unsafe.Slice(unsafe.SliceData(nums), len(nums))numsRef1[0] = 2fmt.Println(nums)
}

解释:

unsafe.SliceData 函数
返回 切片 nums 底层数组的起始位置 的首地址

unsafe.Slice 函数
根据给定的底层数组地址切片长度创建一个新的切片。重要的是,这个新的切片不会进行内存拷贝,而是直接操作底层数组的地址。因此,修改这个新切片的数据会直接影响到原切片的底层数组的数据。


StringData

func StringData(str string) *byte

StringData ,类似SliceData,获取字符串的底层数据指针。返回一个 uintptr 类型的值,代表字符串底层数据的内存地址

示例:

func main() {str := "hello,world!"for ptr, i := unsafe.Pointer(unsafe.StringData(str)), 0; i < len(str); ptr, i = unsafe.Add(ptr, unsafe.Sizeof(byte(0))), i+1 {char := *(*byte)(ptr)fmt.Println(string(char))}
}

String

func String(ptr *byte, len IntegerType) string

String,类似 Slice,将给定的字节数组(指针和长度)转换为一个 string 类型

示例:

func main() {bytes := []byte("hello world")str := unsafe.String(unsafe.SliceData(bytes), len(bytes))fmt.Println(str)
}
http://www.xdnf.cn/news/967861.html

相关文章:

  • gitlab-runner 如何配置使用 Overwrite generated pod specifications
  • 图注意力卷积神经网络GAT在无线通信网络拓扑推理中的应用
  • 第四章 软件需求工程
  • 上位机开发:C# 读写 PLC 数据块数据
  • CppCon 2015 学习:Racing the File System
  • “详规一张图”——上海土地利用数据
  • 《大模型RAG进阶实战训练营毕业总结》
  • 多模态2025:技术路线“神仙打架”,视频生成冲上云霄
  • 雷卯针对易百纳海鸥派海思SD3403 SS928智能视觉AI视觉国产化4K视频开发板防雷防静电方案
  • 香橙派3B学习笔记9:Linux基础gcc/g++编译__C/C++中动态链接库(.so)的编译与使用
  • Vim 匹配跳转与搜索命令完整学习笔记
  • ArcGIS Pro 3.4 二次开发 - 任务
  • word的目录和正文之间存在一张空白纸,目录后面的分节符为什么调不上去?
  • 《函数之恋》
  • STL 4函数对象
  • 工控类UI设计经常接触到10.1寸迪文屏
  • React【回顾】 深层次面试详解:函数式组件核心原理与高级优化
  • 香港科技大学(广州)机器人与自主系统学域(ROAS)2025年度夏令营招募!
  • 《高等数学》(同济大学·第7版)第三章第六节函数图形的描绘
  • 如何判断Cursor邮箱被封?
  • 【Dv3Admin】系统视图角色菜单API文件解析
  • 钉钉告警集成部署指南
  • DataSource学习
  • 【时时三省】(C语言基础)静态局部变量(static局部变量)
  • Visual Studio2022配置OpenCV环境
  • 自定义表单组件面板排序处理
  • 页面渲染流程与性能优化
  • 如何删除导出的xml中的xmlns:xsd=
  • XML Group端口详解
  • RSA算法