当前位置:首页 > 文章列表 > Golang > Go教程 > Golang 中的 unsafe.Pointer 和 uintptr详解

Golang 中的 unsafe.Pointer 和 uintptr详解

来源:脚本之家 2023-02-24 11:00:32 0浏览 收藏

怎么入门Golang编程?需要学习哪些知识点?这是新手们刚接触编程时常见的问题;下面golang学习网就来给大家整理分享一些知识点,希望能够给初学者一些帮助。本篇文章就来介绍《Golang 中的 unsafe.Pointer 和 uintptr详解》,涉及到unsaf、uintptr,有需要的可以收藏一下

uintptr

uintptr 的定义在 builtin 包下,定义如下:

// uintptr is an integer type that is large enough to hold the bit pattern of
// any pointer.
type uintptr uintptr

参照注释我们知道:

  • uintptr 是一个整数类型(这个非常重要),注意,他不是个指针;
  • 但足够保存任何一种指针类型。

unsafe 包支持了这些方法来完成【类型】=> uintptr 的转换:

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

你可以将任意类型变量转入,获取对应语义的 uintptr,用来后续计算内存地址(比如基于一个结构体字段地址,获取下一个字段地址等)。

unsafe.Pointer

我们来看一下什么是 unsafe 包下的 Pointer:

// ArbitraryType is here for the purposes of documentation only and is not actually
// part of the unsafe package. It represents the type of an arbitrary Go expression.
type ArbitraryType int
// Pointer represents a pointer to an arbitrary type. There are four special operations
// available for type Pointer that are not available for other types:
//	- 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.
// Pointer therefore allows a program to defeat the type system and read and write
// arbitrary memory. It should be used with extreme care.
type Pointer *ArbitraryType

这里的 ArbitraryType 仅仅是为了便于开发者理解。语义上来讲你可以把 Pointer 理解为一个可以指向任何一种类型的【指针】。

这一点很关键。我们此前遇到的场景一般都是,先定义一个类型,然后就有了这个类型对应的指针。而 unsafe.Pointer 则是一个通用的解法,不管你是什么类型都可以。突破了这层限制,我们就可以在运行时具备更多能力,也方便适配一些通用场景。

官方提供了四种 Pointer 支持的场景:

  • 任意类型的指针可以转换为一个 Pointer;
  • 一个 Pointer 也可以被转为任意类型的指针;
  • uintptr 可以被转换为 Pointer;
  • Pointer 也可以被转换为 uintptr。

这样强大的能力使我们能够绕开【类型系统】,丢失了编译期的校验,所以使用时一定要小心。

使用姿势

常规类型互转

func Float64bits(f float64) uint64 {
    return *(*uint64)(unsafe.Pointer(&f))
}

我们取 f 的指针,将其转为 unsafe.Pointer,再转为一个 uint64 的指针,最后解出来值。

其实本质就是把 unsafe.Pointer 当成了一个媒介。用到了他可以从任意一个类型转换得来,也可以转为任意一个类型。

这样的用法有一定的前提:

  • 转化的目标类型(uint64) 的 size 一定不能比原类型 (float64)还大(二者size都是8个字节);
  • 前后两种类型有等价的 memory layout;

比如,int8 转为 int64 是不支持的,我们测试一下:

package main
import (
	"fmt"
	"unsafe"
)
func main() {
	fmt.Println("int8 => int64", Int8To64(5))
	fmt.Println("int64 => int8", Int64To8(5))
}
func Int64To8(f int64) int8 {
	return *(*int8)(unsafe.Pointer(&f))
}
func Int8To64(f int8) int64 {
	return *(*int64)(unsafe.Pointer(&f))
}

运行后你会发现,int64 => int8 转换正常,从小到大则会出问题:

int8 => int64 1079252997
int64 => int8 5

Program exited.

Pointer => uintptr

从 Pointer 转 uintptr 本质产出的是这个 Pointer 指向的值的内存地址,一个整型。

这里还是要在强调一下:

  • uintptr 指的是具体的内存地址,不是个指针,没有指针的语义,你可以将 uintptr 打印出来比对地址是否相同。
  • 即便某个对象因为 GC 等原因被回收,uintptr的值也不会连带着变动。
  • uintptr地址关联的对象可以被垃圾回收。GC不认为uintptr是活引用,因此unitptr地址指向的对象可以被垃圾收集。

指针算数计算:Pointer => uintptr => Pointer

将一个指针转为 uintptr 将会得到它指向的内存地址,而我们又可以结合 SizeOf,AlignOf,Offsetof 来计算出来另一个 uintptr 进行计算。

这类场景最常见的是【获取结构体中的变量】或【数组中的元素】。

比如:

f := unsafe.Pointer(&s.f) 
f := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f))

e := unsafe.Pointer(&x[i])
e := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + i*unsafe.Sizeof(x[0]))

上面这两组运算本质是相同的,一种是直接拿地址,一种是通过计算 size,offset 来实现。

注意:变量到 uintptr 的转换以及计算必须在一个表达式中完成(需要保证原子性):

错误的案例:

u := uintptr(p)
p = unsafe.Pointer(u + offset)

uintptr 到 Pointer 的转换一定要在一个表达式,不能用 uintptr 存起来,下个表达式再转。

uintptr + offset 算地址,再跟 Pointer 转化其实是一个很强大的能力,我们再来看一个实际的例子:

package main
import (
	"fmt"
	"unsafe"
)
func main() {
	length := 6
	arr := make([]int, length)
	for i := 0; i 

unsafe.Pointer 不能进行算数计算,uintptr 其实是很好的一个补充。

reflect 包中从 uintptr => Ptr

我们知道,reflect 的 Value 提供了两个方法 Pointer 和 UnsafeAddr 返回 uintptr。这里不使用 unsafe.Pointer 的用意在于避免用户不 import unsafe 包就能将结果转成任意类型,但这也带来了问题。

上面有提到,千万不能先保存一个 uintptr,再转 unsafe.Pointer,这样的结果是很不可靠的。所以我们必须在调用完 Pointer/UnsafeAddr 之后就立刻转 unsafe.Pointer。

正例:

p := (*int)(unsafe.Pointer(reflect.ValueOf(new(int)).Pointer()))

反例:

u := reflect.ValueOf(new(int)).Pointer()
p := (*int)(unsafe.Pointer(u))

实战案例

string vs []byte

活学活用,其实参照上面转换的第一个案例就可以实现,不需要 uintptr。还是一样的思路,用 unsafe.Pointer 作为媒介,指针转换结束后,解指针拿到值即可。

import (
	"unsafe"
)
func BytesToString(b []byte) string {
	return *(*string)(unsafe.Pointer(&b))
}
func StringToBytes(s string) []byte {
	return *(*[]byte)(unsafe.Pointer(&s))
}

其实这里从 []byte 转 string 的操作就是和 strings 包下 Builder 的设计一致的:

// A Builder is used to efficiently build a string using Write methods.
// It minimizes memory copying. The zero value is ready to use.
// Do not copy a non-zero Builder.
type Builder struct {
	addr *Builder // of receiver, to detect copies by value
	buf  []byte
}
// String returns the accumulated string.
func (b *Builder) String() string {
	return *(*string)(unsafe.Pointer(&b.buf))
}

// Reset resets the Builder to be empty.
func (b *Builder) Reset() {
	b.addr = nil
	b.buf = nil
}

// Write appends the contents of p to b's buffer.
// Write always returns len(p), nil.
func (b *Builder) Write(p []byte) (int, error) {
	b.copyCheck()
	b.buf = append(b.buf, p...)
	return len(p), nil
}

// WriteString appends the contents of s to b's buffer.
// It returns the length of s and a nil error.
func (b *Builder) WriteString(s string) (int, error) {
	b.copyCheck()
	b.buf = append(b.buf, s...)
	return len(s), nil
}

strings.Builder 设计之处就是为了最大程度降低内存拷贝。本质是维护了一个 buf 的字节数组。

sync.Pool

sync.Pool 的设计中在本地 pool 没有可以返回 Get 的元素时,会到其他 poolLocal 偷一个元素回来,这个跳转到其他 pool 的操作就是用 unsafe.Pointer + uintptr + SizeOf 实现的,参考一下:

func indexLocal(l unsafe.Pointer, i int) *poolLocal {
	lp := unsafe.Pointer(uintptr(l) + uintptr(i)*unsafe.Sizeof(poolLocal{}))
	return (*poolLocal)(lp)
}

今天关于《Golang 中的 unsafe.Pointer 和 uintptr详解》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于golang的内容请关注golang学习网公众号!

版本声明
本文转载于:脚本之家 如有侵犯,请联系study_golang@163.com删除
Go语言异步API设计的扇入扇出模式详解Go语言异步API设计的扇入扇出模式详解
上一篇
Go语言异步API设计的扇入扇出模式详解
Go编译原理之函数内联
下一篇
Go编译原理之函数内联
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ljg-skills -
    ljg-skills
    ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
    3081次使用
  • MELO音乐 - AI 音乐生成平台,支持多模态创作能力
    MELO音乐
    MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
    2840次使用
  • UniScribe - AI 免费在线音视频转文字平台
    UniScribe
    UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
    2785次使用
  • 剧云 - 免费 AI 智能中文剧本创作平台
    剧云
    剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
    3004次使用
  • 万象有声 - AI 一站式有声内容创作平台
    万象有声
    万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
    2957次使用
查看更多
相关文章
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码