golang 如何使用指针灵活操作内存?unsafe 包原理解析
Hi 你好,我是 k 哥。一个大厂工作 6 年,还在继续搬砖的后端程序员。
我们都知道,C/C++提供了强大的万能指针 void*,任何类型的指针都可以和万能指针相互转换。并且指针还可以进行加减等算数操作。那么在 Golang 中,是否有类似的功能呢?答案是有的,这就是我们今天要探讨的 unsafe 包。
本文将深入探讨 unsafe 包的功能和原理。同时,我们学习某种东西,一方面是为了实践运用,另一方面则是出于功利性面试的目的。所以,本文还会为大家介绍 unsafe 包的典型应用以及高频面试题。
功能
为了实现灵活操作内存的目的,unsafe 包主要提供了 4 个功能:
定义了 Pointer 类型,任何类型的指针都可和 Pointer 互相转换,类似于 c 语言中的 void*
定义了 uintptr 类型,Pointer 和 uintptr 可以互相转换, 从而实现指针的加减等算数运算。
uintptr 是用于指针运算的,它只是一个存储一个 指针地址
的 int
类型,GC 不把 uintptr 当指针,因此, uintptr 类型的目标可能会被回收
获取任意类型内存对齐、偏移量和内存大小。
Alignof 返回类型 x 的内存地址对齐值 m,这个类型在内存中的地址必须是 m 的倍数(基于内存读写性能的考虑)。
Offsetof 返回结构体成员 x 在内存中的位置离结构体起始处(结构体的第一个字段的偏移量都是 0)的字节数,即偏移量。
Sizeof 返回类型 x 所占据的字节数,如果类型 x 结构有指针,Sizeof 不包含 x 指针成员所指向内容的大小。
ArbitraryType 是占位符,golang 编译器在编译时会替换为具体类型
高性能类型转换。
Slice 传入任意类型的指针和长度,返回该类型 slice 变量
SliceData 传入任意类型的 slice 变量,返回该 slice 底层数组的指针。
String 从一个 byte 指针派生出一个指定长度的字符串。
StringData 用来获取一个字符串底层字节序列中的第一个 byte 的指针。
高性能类型转换原理
为什么说 Slice、SliceData、String、StringData 是高性能类型转换函数呢?下面我们就来剖析下它们的实现原理。
本文以 String 和 StringData 函数为例,Slice 和 SliceData 函数实现原理类似。在介绍函数实现原理之前,先认识下 string 类型的底层数据结构 StringHeader。string 类型会被 Golang 编译器编译成此结构,其中 Data 是 byte 数组地址,Len 是字符串长度。
String 函数会被 Go 编译成下面的函数实现逻辑。我们可以发现,ptr 指针转换为 string 类型,是直接将 ptr 赋值给 StringHeader 的成员 Data,而不需要重新拷贝 ptr 指向的 byte 数组。从而通过零拷贝实现高性能类型转换。
StringData 函数会被 Go 编译成下面的函数实现逻辑。同理,我们可以发现,string 类型转换为 byte,是直接取 StringHeader 的 uintptr 类型成员 Data,并将其转换为 byte。不需要拷贝整个 string,重新生成 byte 数组。从而通过零拷贝实现高性能类型转换。
回到问题,为什么说 Slice、SliceData、String、StringData 是高性能类型转换函数呢?通过 String 和 StringData 函数的实现逻辑,我们可以知道,String 和 StringData 利用 unsafe 包,通过零拷贝,实现了高性能类型转换。
典型应用
在实践中,常见使用 unsafe 包的场景有 2 个:
与操作系统以及非 go 编写(cgo)的代码通信。
高性能类型转换。
高频面试题
能说说 uintptr 和 unsafe.Pointer 的区别吗?
字符串转成 byte 数组,会发生内存拷贝吗?
欢迎大家关注我的公粽号【golang 架构师 k 哥】,每周分享 golang 和架构师技能。
版权声明: 本文为 InfoQ 作者【golang架构师k哥】的原创文章。
原文链接:【http://xie.infoq.cn/article/85973a9f2ff1413706784b721】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论