标准库unsafe:带你突破golang中的类型限制
本文分享自华为云社区《突破语言golang中的类型限制》,作者:码乐。
1 简介
在使用c语言编程时,常常因为类型的问题大伤脑筋,而其他语言比如java,python默认类型又是难以改变的,golang提供了一些方式用于喜欢hack的用户。

2 标准库unsafe的简单介绍
官方说明标准库 unsafe 包含绕过 Go 程序的类型安全的操作。
导入unsafe包可能是不可移植的,并且不受 Go 1 兼容性指南的保护。
在1.20中,标准库的unsafe包很小, 二个结构体类型,八个函数,在一个文件中。
package unsage
type ArbitraryType int
type IntegerType int
type Pointer *ArbitraryType
func Sizeof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr
func Add(ptr Pointer, len IntegerType) Pointer
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
func SliceData(slice []ArbitraryType) *ArbitraryType
func String(ptr *byte, len IntegerType) string
func StringData(str string) *byte
unsafe包定义了 二个类型和 八个函数,二个类型 ArbitraryType 和 IntegerType 不真正属于unsafe包,我们在Go代码中并不能使用它们定义变量。
它表示一个任意表达式的类型,仅用于文档目的,Go编译器会对其做特殊处理。
虽然位于 unsafe,但是 Alignof,Offsetof,Sizeof,这三个函数的使用是绝对安全的。 以至于Go设计者Rob pike提议移走它们。
这三个函数的共同点是 都返回 uintptr 类型。
之所以使用 uintptr 类型而不是 uint64 整型,因为这三个函数更多应用于 有 unsafe.Pointer和 uintptr类型参数的指针运算。
采用uintptr做为返回值类型可以减少指针运算表达式的显式类型转换。
2.1 获取大小 Sizeof
Sizeof 用于获取一个表达式的大小。 该函数获取一个任意类型的表达式 x,并返回 按bytes计算 的大小,假设变量v,并且v通过 v =x声明。
Sizeof 接收任何类型的表达式x,并返回以bytes字节为单位的大小, 并且假设变量v是通过var v = x声明的。该大小不包括任何可能被x引用的内存。
例如,如果x是一个切片,Sizeof返回切片描述符的大小,而不是该片所引用的内存的大小。
对于一个结构体,其大小包括由字段对齐引入的任何填充。
如果参数x的类型没有变化,不具有可变的大小,Sizeof的返回值是一个Go常数不可变值 。
(如果一个类型是一个类型参数,或者是一个数组,则该类型具有可变的大小或结构类型中的元素大小可变)。
示例:
var (
i int = 5
a = [10]int{}
ss = a[:]
f FuncFoo preValue = map[string]uintptr{
"i": 8,
"a": 80,
"ss": 24,
"f": 48,
"f.c": 10,
"int_nil": 8,
}
) type FuncFoo struct {
a int
b string
c [10]byte
d float64
}
func TestFuncSizeof(t *testing.T) {
defer setUp(t.Name())()
fmt.Printf("\tExecute test:%v\n", t.Name()) if unsafe.Sizeof(i) != preValue["i"] {
ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["i"]), t)
} if unsafe.Sizeof(a) != preValue["a"] {
ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["a"]), t) } if unsafe.Sizeof(ss) != preValue["ss"] {
ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["ss"]), t) }
if unsafe.Sizeof(f) != preValue["f"] {
ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["f"]), t) }
if unsafe.Sizeof(f.c) != preValue["f.c"] {
ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["f.c"]), t) }
if unsafe.Sizeof(unsafe.Sizeof((*int)(nil))) != preValue["int_nil"] {
ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t) }
}
Sizeof 函数不支持之间传入无类型信息的nil值,如下错误
unsafe.Sizeof(nil)
我们必须显式告知 Sizeof 传入的nil究竟是那个类型,
unsafe.Sizeof(unsafe.Sizeof((*int)(nil)))
必须显式告知nil是哪个类型的nil,这就是传入一个值 nil 但是类型明确的变量。
对齐系数 Alignof 用于获取一个表达式的内地地址对齐系数,对齐系数 alignment factor 是一个计算机体系架构 computer architecture 层面的术语。
在不同计算机体系中,处理器对变量地址都有对齐要求,即变量的地址必须可被该变量的对齐系数整除。
它接收一个任何类型的表达式x,并返回所需的排列方式 假设变量v是通过var v = x声明的。
它是m一个最大的值。
例1,
a = [10]int{}
reflect.TypeOf(x).Align() //8
unsafe.Alignof(a) //8
它与reflect.TypeOf(x).Align()返回的值相同。
作为一个特例,如果一个变量s是结构类型,f是一个字段,那么Alignof(s.f)将返回所需的对齐方式。
该类型的字段在结构中的位置。这种情况与reeflect.TypeOf(s.f).FieldAlign()返回的值。
Alignof的返回值是一个Go常数,如果参数的类型不具有可变大小。
(关于可变大小类型的定义,请参见[Sizeof]的描述)。
继上 例2:
var (
i int = 5
a = [10]int{}
ss = a[:]
f FuncFoo
zhs = "文" preValue = map[string]uintptr{
"i": 8,
"a": 80,
"ss": 24,
"f": 48,
"f.c": 10,
"int_nil": 8,
}
) func TestAlignof(t *testing.T) { defer setUp(t.Name())()
fmt.Printf("\tExecute test:%v\n", t.Name()) var x int b := uintptr(unsafe.Pointer(&x))%unsafe.Alignof(x) == 0
t.Log("alignof:", b) if unsafe.Alignof(i) != preValue["i"] {
ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t) } if unsafe.Alignof(a) != preValue["i"] {
ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t) } if unsafe.Alignof(ss) != preValue["i"] {
ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t) } if unsafe.Alignof(f.a) != preValue["i"] {
ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t) } if unsafe.Alignof(f) != preValue["i"] {
ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t) }
中文对齐系数 为 8
if unsafe.Alignof(zhs) != preValue["i"] {
ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["i"]), t)
}
空结构体对齐系数 1
if unsafe.Alignof(struct{}{}) != 1 {
ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), 1), t)
}
byte 数组对齐系数为 1
if unsafe.Alignof(sbyte) != 1 {
ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), 1), t)
}
长度为0 的数组,与其元素的对齐系数相同
if unsafe.Alignof([0]int{}) != 8 {
ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), 8), t)
}
长度为0 的数组,与其元素的对齐系数相同
if unsafe.Alignof([0]struct{}{}) != 1 {
ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), 1), t)
}
}
执行它:
go test -timeout 30s -run ^TestAlignof$ ./unsafe_case.go
对齐系数 alignment factor,变量的地址必须可被该变量的对齐系数整除。
2.2 使用对齐的例子
我们使用相同字段,分别创建两个结构体属性分别为对齐或不对齐,帮助 go 更好地分配内存和 使用cpu读取,查看效果
type RandomResource struct {
Cloud string // 16 bytes
Name string // 16 bytes
HaveDSL bool // 1 byte
PluginVersion string // 16 bytes
IsVersionControlled bool // 1 byte
TerraformVersion string // 16 bytes
ModuleVersionMajor int32 // 4 bytes
}
type OrderResource struct {
ModuleVersionMajor int32 // 4 bytes
HaveDSL bool // 1 byte
IsVersionControlled bool // 1 byte
Cloud string // 16 bytes
Name string // 16 bytes
PluginVersion string // 16 bytes
TerraformVersion string // 16 bytes
}
字段 存储使用的空间与 字段值没有关系
var d RandomResource
d.Cloud = "aws-singapore"
... InfoHandler(fmt.Sprintf("随机顺序属性的结构体内存 总共占用 StructType: %T => [%d]\n", d, unsafe.Sizeof(d)), m)
var te = OrderResource{}
te.Cloud = "aws-singapore"
...
m.Logf("属性对齐的结构体内存 总共占用 StructType:d %T => [%d]\n", te, unsafe.Sizeof(te))
然后复制结构体,并改变其属性值,查看存储空间和值的长度变化
te2 := te
te2.Cloud = "ali2"
m.Logf("结构体2 te2:%#v\n", &te2)
m.Logf("结构体1 te:%#v\n", &te) m.Log("改变 te3 将同时改变 te,te3 指向了 te的地址")
m.Log("复制了对齐结构体,并重新赋值,用于查看字段长度。")
m.Log("(*te).Cloud:", (te).Cloud, "*te.Cloud", te.Cloud, "te size:", unsafe.Sizeof(te.Cloud), "te value len:", len(te.Cloud)) te3 := &te
te3.Cloud = "HWCloud2" m.Log("(*te3).Cloud:", (*te3).Cloud, "*te3.Cloud", te3.Cloud, "te3 size:", unsafe.Sizeof(te3.Cloud), "te3 value len:", len(te3.Cloud))
m.Logf("字段 Cloud:%v te3:%p\n", (*te3).Cloud, te3)
m.Logf("字段 Cloud:%v order:%v te:%v, addr:%p\n", te.Cloud, (te).Cloud, te, &te)
执行它,
go test -v .\case_test.go
得到以下输出:
随机顺序属性的结构体内存 总共占用 StructType: main.Raesource => [88]
...
属性对齐的结构体内存 总共占用 StructType:d main.OrderResource => [72]
改变 te3 将同时改变 te,te3 指向了 te的地址
case_test.go:186: 复制了对齐结构体,并重新赋值,用于查看字段长度。
case_test.go:188: (*te).Cloud: aws-singapore *te.Cloud aws-singapore te size: 16 te Alignof: 8 te value len: 13 reflect Align len and field Align len: 8 8
case_test.go:190: (*te2).Cloud: ali2 *te2.Cloud aws-singapore te2 size: 16 te2 Alignof: 8 te2 value len: 4 reflect Align len and field Align len: 8 8
case_test.go:196: (*te3).Cloud: HWCloud2-asia-southeast-from-big-plant-place-air-local-video-service-picture-merge-from-other-all-company *te3.Cloud HWCloud2-asia-southeast-from-big-plant-place-air-local-video-service-picture-merge-from-other-all-company te3
size: 16 te3 Alignof: 8 te3 value len: 105 reflect Align len and field Align len: 8 8
case_test.go: 结构体1字段 Cloud:HWCloud2-asia-southeast-from-big-plant-place-air-local-video-service-picture-merge-from-other-all-company te2:0xc0000621e0
case_test.go:198: 结构体2字段 Cloud:ali2 te2:0xc000062280
case_test.go:199: 结构体3字段 Cloud:HWCloud2-asia-southeast-from-big-plant-place-air-local-video-service-picture-merge-from-other-all-company te3:0xc0000621e0
小结
我们介绍了unsafe包的检查功能,在初始化时,go结构体已经分配了对于的内存空间,
一个结构体而言,结构体属性为随机顺序的,go将分配更多内存空间。 即使是复制后。
比如 结构体的Cloud 字段。
Sizeof表达式大小总是16,
而对齐系数 Alignof 大小总是8,
而在不同的结构体实例中值长度可以为 4,13, 105.
本节源码地址:
https://github.com/hahamx/examples/tree/main/alg_practice/2_sys_io
标准库unsafe:带你突破golang中的类型限制的更多相关文章
- 学习使用 ARM 的 math 库,据说 速度比C标准库 自带的 快 几十倍 到几百倍
1.首先 添加 库 到 工程 ,路径 如下 C:\Keil\ARM\CMSIS\Lib\ARM 2.包含头文件以及在 工程里 添加 头文件 路径如下 C:\Keil\ARM\CMSIS\Includ ...
- golang中函数类型
今天看Martini文档,其功能列表提到完全兼容http.HandlerFunc接口,就去查阅了Go: net/http的文档,看到type HandlerFunc这部分,顿时蒙圈了.由于之前学习的时 ...
- golang中基本类型存储大小和转换
Go语言的基本类型有: bool string int.int8.int16.int32.int64 uint.uint8.uint16.uint32.uint64.uintptr byte // u ...
- golang中值类型/指针类型的变量区别总结
转自:https://segmentfault.com/a/1190000012329213 值类型的变量和指针类型的变量 先声明一个结构体: type T struct { Name string ...
- golang中接口类型小案例
1. 在项目中实现注册成功之后,向用户发送邮件.微信提醒 package main import "fmt" type IMessage interface { send() b ...
- golang中值类型的嵌入式字段和指针类型的嵌入式字段
总结: 1. 值类型的嵌入式字段,该类型拥有值类型的方法集,没有值指针类型的方法集 2. 指针类型的嵌入式字段,该类型拥有值指针类型的方法集,没有值类型的方法集,并且,该类型的指针类型也有值指针类型的 ...
- Go 标准库 —— sync.Mutex 互斥锁
Mutex 是一个互斥锁,可以创建为其他结构体的字段:零值为解锁状态.Mutex 类型的锁和线程无关,可以由不同的线程加锁和解锁. 方法 func (*Mutex) Lock func (m *Mut ...
- 通过atomic_flag简单自旋锁实现简单说明标准库中锁使用的memory_order
在使用标准库中的加锁机制时,例如我们使用std::mutex,写了如下的代码(下面的代码使用condition_variable可能更合适) std::mutex g_mtx; int g_resNu ...
- 把《c++ primer》读薄(3-2 标准库vector容器+迭代器初探)
督促读书,总结精华,提炼笔记,抛砖引玉,有不合适的地方,欢迎留言指正. 标准库vector类型初探,同一种类型的对象的集合(类似数组),是一个类模版而不是数据类型,学名容器,负责管理 和 存储的元素 ...
- C++ 标准库类型-String,Vector and Bitset
<C++ Primer 4th>读书摘要 最重要的标准库类型是 string 和 vector,它们分别定义了大小可变的字符串和集合.这些标准库类型是语言组成部分中更基本的那些数据类型(如 ...
随机推荐
- org.apache.http.client.ClientProtocolException: URI does not specify a valid host name:localhost:xxx
今天部署应用的时候遇到的,总结一下我知道的有2个原因: 1.地址前要加http:// 这就是标题报错的原因,他用的是localhost:xxx 2.地址本身拼错了也会报这个,例如地址:http:// ...
- websocket 监听端口问题
有用户反馈使用我们的软件时会出现 ERR_CONNECTION_CLOSED 问题 我们根据反馈排查问题发现是软件的 websocket 监听端口被另一款软件占用了,暂时的解决方法是将占用的软件关闭 ...
- Golang Web 框架 Gin 基础学习教程集合目录
Gin Web 框架基础学习系列目录 01-quickstart 02-parameter 03-route 04-middleware 05-log 06-logrus 07-bind 08-val ...
- 2021-07-30 JavaScript中常用数据的判断
为什么要判断一个变量的常用数据? 实际业务场景里,一个变量的数据是否合法或符合预期,会影响到项目中用到的UI组件库特定组件的运行.比如element-ui中的el-select组件,单选时绑定的数据不 ...
- 硬件开发笔记(十四):RK3568底板电路LVDS模块、MIPI模块电路分析、LVDS硬件接口、MIPI硬件接口详解
前言 本篇继续分析底板原理图mipi/lvds屏幕电路原理图.硬件接口详解. LVDS与MIPI的区别 液晶屏有RGB TTL.LVDS.MIPI.HDMI接口,这些接口区别于信号的类型( ...
- Windows Docker Destop修改默认镜像文件位置
0.首先关闭docker destop. 1.通过Everything或者资源管理器找到以.vhdx结尾的文件所在的位置,这些就是docker镜像路径 2.我的路径:C:\Users\Administ ...
- 教你如何用Keepalived和HAproxy配置高可用 Kubernetes 集群
本文分享自华为云社区<使用 Keepalived 和 HAproxy 创建高可用 Kubernetes 集群>,作者:江晚正愁余. 高可用 Kubernetes 集群能够确保应用程序在运行 ...
- 大众点评cat报警源码
类时序 时许说明 判断是否是报警机器. 1分钟启动一个线程根据设置的报警条件,时间段去查询CAT报告数据. 根据返回的报告数据,逐层解析TYPE,NAME,RANGE中的数据是否满足报警条件. 只有全 ...
- 【Application Insights】使用Powershell命令向Application Insgihts发送测试数据
问题描述 在昨天的文章中,介绍了 "[Application Insights]使用CURL命令向Application Insgihts发送测试数据",今天则继续实验通过Powe ...
- 【Azure Redis 缓存】当使用Azure Redis 集群服务时候,发生了Moved的几点分析
问题描述 当使用Azure Redis 集群服务时候,发生了Moved的几点分析 问题分析 1. 关于 Moved 问题,原因有可能是内存碎片整理,从而引起Redis发生failover. 2. ...