unsafe内容介绍

type ArbitraryType int
type Pointer *ArbitraryType
func Sizeof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr

unsafe包只有两个类型,三个函数,但是功能很强大。

unsafe 库让 golang 可以像C语言一样操作计算机内存,但这并不是golang推荐使用的,能不用尽量不用,就像它的名字所表达的一样,它绕过了golang的内存安全原则,是不安全的,容易使你的程序出现莫名其妙的问题,不利于程序的扩展与维护。

先简单介绍下Golang指针

  1. *类型:普通指针,用于传递对象地址,不能进行指针运算。

  2. unsafe.Pointer:通用指针类型,用于转换不同类型的指针,不能进行指针运算。

  3. uintptr:用于指针运算,GC 不把 uintptr 当指针,uintptr 无法持有对象。uintptr 类型的目标会被回收

  unsafe.Pointer 可以和 普通指针 进行相互转换。

  unsafe.Pointer 可以和 uintptr 进行相互转换。

也就是说 unsafe.Pointer 是桥梁,可以让任意类型的指针实现相互转换,也可以将任意类型的指针转换为 uintptr 进行指针运算。

uintptr这个类型,在golang中,字节长度也是与int一致。通常Pointer不能参与运算,比如你要在某个指针地址上加上一个偏移量,Pointer是不能做这个运算的,那么谁可以呢?就是uintptr类型了,只要将Pointer类型转换成uintptr类型,做完加减法后,转换成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 type Pointer *ArbitraryType

ArbitraryType是int的一个别名,在Go中对ArbitraryType赋予特殊的意义。代表一个任意Go表达式类型。

Pointer 是int指针类型的一个别名,在Go中可以把Pointer类型,理解成任何指针的父类型。

下面为官方文档中对Pointer的使用场景介绍

Pointer represents a pointer to an arbitrary type. There are four special operationsavailable 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.

golang的指针类型长度与int类型长度,在内存中占用的字节数是一样的.

ArbitraryType类型的变量也可以是指针。所以,千万不要死磕type后边的那个int

三个函数简介

// Sizeof takes an expression x of any type and returns the size in bytes
// of a hypothetical variable v as if v was declared via var v = x.
// The size does not include any memory possibly referenced by x.
// For instance, if x is a slice, Sizeof returns the size of the slice
// descriptor, not the size of the memory referenced by the slice.
**func Sizeof(x ArbitraryType) uintptr** // Offsetof returns the offset within the struct of the field represented by x,
// which must be of the form structValue.field. In other words, it returns the
// number of bytes between the start of the struct and the start of the field.
**func Offsetof(x ArbitraryType) uintptr** // Alignof takes an expression x of any type and returns the required alignment
// of a hypothetical variable v as if v was declared via var v = x.
// It is the largest value m such that the address of v is always zero mod m.
// It is the same as the value returned by reflect.TypeOf(x).Align().
// As a special case, if a variable s is of struct type and f is a field
// within that struct, then Alignof(s.f) will return the required alignment
// of a field of that type within a struct. This case is the same as the
// value returned by reflect.TypeOf(s.f).FieldAlign().
**func Alignof(x ArbitraryType) uintptr**

通过分析发现,这三个函数的参数均是ArbitraryType类型,就是接受任何类型的变量。

  1. Alignof返回变量对齐字节数量
  2. Offsetof返回变量指定属性的偏移量,这个函数虽然接收的是任何类型的变量,但是有一个前提,就是变量要是一个struct类型,且还不能直接将这个struct类型的变量当作参数,只能将这个struct类型变量的属性当作参数。
  3. Sizeof 返回变量在内存中占用的字节数,切记,如果是slice,则不会返回这个slice在内存中的实际占用长度

示例

Sizeof

unsafe.Sizeof函数返回的就是uintptr类型的值(表达式,即值的大小):

var p float64 = 99
fmt.Println(reflect.TypeOf(unsafe.Sizeof(p)))
fmt.Println(unsafe.Sizeof(p))

results:

uintptr
8

unsafe.Sizeof接受任意类型的值(表达式),返回其占用的字节数,在上面的例子中float64的大小是8bytes。

如果传入一个指针类型的对象会返回多少呢?

type W struct {
a byte
b int32
c int64
} var w *W
fmt.Println(unsafe.Sizeof(w)) //4 or 8

一般情况下,可能是4或8,因为w是指针类型uintptr,而uintptr是平台相关的,在32位系统下大小是4bytes,在64位系统下是8bytes。

要获取值类型的大小,需要对指针变量进行取值:

fmt.Println(unsafe.Sizeof(*w)) //16

对齐

在上面的例子中,*w的大小为16,按照常理来说,byte占用1字节,int32占用4字节,int64占用8字节,大小应该是13才对。这是因为发生了对齐,unsafe.Alignof可以计算对齐值:

unsafe.Alignof(w.a)   // type byte
unsafe.Alignof(w.b) // type int32
unsafe.Alignof(w.c) // type int64

分别是1、4、8,因为int32类型的对齐值是4bytes,必须是4的倍数,故byte类型要填充3个字节。而填充后,两者的大小和为8bytes,int64对齐值是8bytes,不需要填充,所以用unsafe.Sizeof获取到结构的大小为4+4+8=16。

反射包的对齐方法

反射包也有某些方法可用于计算对齐值:

unsafe.Alignof(w)等价于reflect.TypeOf(w).Align。

unsafe.Alignof(w.i)等价于reflect.Typeof(w.i).FieldAlign()。

结构体的对齐值

如果我们计算的是结构体的对齐值而不是某个字段或者基本类型,那么值会是多少呢?

type W struct {
a byte
b int32
c int64
} var w *W
var w2 W fmt.Println(unsafe.Alignof(w))
fmt.Println(unsafe.Alignof(w2))
fmt.Println(reflect.TypeOf(w).Elem().Align())

results:

8
8
8

64位机器下,指针对象的对齐值是8,因为指针类型是uintptr。而结构体的值类型却是8bytes的对齐值,这是因为会先进行字段的对齐,字段最大的对齐值是8bytes,因此结构体值类型的对齐值也是8。

更改结构,验证一下:

type W struct {
a byte
b int32
c int32
}
var w W
fmt.Println(unsafe.Alignof(w)) //4

综合示例

type T struct {
t1 byte
t2 int32
t3 int64
t4 string
t5 bool
} fmt.Println("----------unsafe.Pointer---------")
t := &T{1, 2, 3, "this is a example", true}
ptr := unsafe.Pointer(t)
t1 := (*byte)(ptr)
fmt.Println(*t1)
t2 := (*int32)(unsafe.Pointer(uintptr(ptr) + unsafe.Offsetof(t.t2)))
*t2 = 99
fmt.Println(t)
t3 := (*int64)(unsafe.Pointer(uintptr(ptr) + unsafe.Offsetof(t.t3)))
fmt.Println(*t3)
*t3 = 123
fmt.Println(t)

results:

----------unsafe.Pointer---------
1
&{1 99 3 this is a example true}
3
&{1 99 123 this is a example true}

借助于 unsafe.Pointer,我们实现了像 C 语言中的指针偏移操作。可以看出,这种不安全的操作使得我们可以在任何地方直接访问结构体中未公开的成员,只要能得到这个结构体变量的地址。

参考资料:

  1. https://blog.csdn.net/libing_thinking/article/details/49907815
  2. https://studygolang.com/articles/6903
  3. https://blog.csdn.net/hzwy23/article/details/60893765
  4. https://www.cnblogs.com/golove/p/5909968.html
  5. https://studygolang.com/articles/1414

Go语言之unsafe包介绍及使用的更多相关文章

  1. go语言的unsafe包(转)

    The unsafe Package in Golang Golang的unsafe包是一个很特殊的包. 为什么这样说呢? 本文将详细解释. 来自go语言官方文档的警告 unsafe包的文档是这么说的 ...

  2. unsafe包的学习和使用

    Go语言之unsafe包介绍及使用 unsafe内容介绍 type ArbitraryType int type Pointer *ArbitraryType func Sizeof(x Arbitr ...

  3. 深度解密Go语言之unsafe

    目录 指针类型 什么是 unsafe 为什么有 unsafe unsafe 实现原理 unsafe 如何使用 获取 slice 长度 获取 map 长度 map 源码中的应用 Offsetof 获取成 ...

  4. R语言︱H2o深度学习的一些R语言实践——H2o包

    每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- R语言H2o包的几个应用案例 笔者寄语:受启发 ...

  5. [转]Go里面的unsafe包详解

    Golang的unsafe包是一个很特殊的包. 为什么这样说呢? 本文将详细解释. 来自go语言官方文档的警告 unsafe包的文档是这么说的: 导入unsafe的软件包可能不可移植,并且不受Go 1 ...

  6. Go语言基础之包

    Go语言基础之包 在工程化的Go语言开发项目中,Go语言的源码复用是建立在包(package)基础之上的.本文介绍了Go语言中如何定义包.如何导出包的内容及如何导入其他包. Go语言的包(packag ...

  7. 使用R语言的RTCGA包获取TCGA数据--转载

    转载生信技能树 https://mp.weixin.qq.com/s/JB_329LCWqo5dY6MLawfEA TCGA数据源 - R包RTCGA的简单介绍 - 首先安装及加载包 - 指定任意基因 ...

  8. Spring4相关jar包介绍(转)

    Spring4相关jar包介绍 spring-core.jar(必须):这个jar 文件包含Spring 框架基本的核心工具类.Spring 其它组件要都要使用到这个包里的类,是其它组件的基本核心,当 ...

  9. R语言中常用包(二)

    数据导入 以下R包主要用于数据导入和保存数据 feather:一种快速,轻量级的文件格式.在R和python上都可使用readr:实现表格数据的快速导入.中文介绍可参考这里readxl:读取Micro ...

随机推荐

  1. Luogu4921/4931 情侣?给我烧了! 组合、递推

    4921 4931 第一眼看着就像容斥,但是容斥不怎么好做-- 第二眼想到错排,结果错排公式糊上去错了-- 不难考虑到可以先选\(K\)对情侣坐在一起,剩下\(N-K\)对错排 选\(K\)对情侣坐在 ...

  2. React-Route的属性exact

    exact是Route下的一条属性,一般而言,react路由会匹配所有匹配到的路由组价,exact能够使得路由的匹配更严格一些. exact的值为bool型,为true是表示严格匹配,为false时为 ...

  3. nginx 安装问题

    yum -y install  xxx pcre-devel  openssl-devel   zlib-devel  这个三个包需要 有时候,我们需要单独安装nginx,来处理大量的下载请求.单独在 ...

  4. C# 队列和栈 线程安全

    队列是其元素以先进先出(FIFO)的方式来处理集合,先入队的元素会先读取. 栈是和队列非常类似的另一个容器,栈和队列最大的区别是后进先出(LIFO),也可以说成先进后出. 队列在现实生活中的例子数不胜 ...

  5. A2dp连接流程源码分析

    在上一篇文章中,我们已经分析了:a2dp初始化流程 这篇文章主要分析a2dp的连接流程,其中还是涉及到一些底层的profile以及protocol,SDP.AVDTP以及L2CAP等. 当蓝牙设备与主 ...

  6. maven docker 插件集成的几个小坑

    昨晚看springboot视频的时候,发现可以使用docker-maven-plugin这个插件直接build出 docker 镜像到远程服务器上,感觉很方便,于是自己也试了一下,但是碰到了几个问题, ...

  7. (10)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Ocelot+Identity Server

    用 JWT 机制实现验证的原理如下图:  认证服务器负责颁发 Token(相当于 JWT 值)和校验 Token 的合法性. 一. 相关概念 API 资源(API Resource):微博服务器接口. ...

  8. Http指南(1)

    网关:是一种特殊的服务器,作为其他服务器的中间实体使用; Agent代理:所有发布web请求的应用程序都是HTTP Agent代理.Web浏览器其实就是一种代理; HTTP报文是在HTTP应用程序之间 ...

  9. .net 2.0 使用linq

    .net 2.0 使用linq,主要是使用Linq to Object,没有测试Linq to XML. 方法: 新建一个net2.0的程序,然后添加对System.Core.Dll的引用.引用时vs ...

  10. 2019年以后ArcGIS 调用天地图的资源URL

    2019年1月1日起,天地图做出如下变更,导致直接在Arcgis/ArcMap中添加WMTS服务不能用了. 国家天地图解释的很清楚,注册个人用户就可以了. 原有调用方式不变,只要在URL 后添加“&a ...