深度探索Go语言(二) unsafe包

用到了unsafe.Pointer进行指针的强制类型转换和指针运算,实际上就是人为地干预编译器对内存地址的解释方式,这些能力对于研究语言的底层实现来讲是不可或缺的。

用到了unsafe.Pointer进行指针的强制类型转换和指针运算,实际上就是人为地干预编译器对内存地址的解释方式,这些能力对于研究语言的底层实现来讲是不可或缺的。

Slice Header结构只是比String Header结构多了一个容量字段,相当于内嵌了一个String Header

 用这种强制类型转换的方式可以避免额外的内存分配,从而减少程序的开销,但是也会带来一些风险。

但是也会带来一些风险。因为按照Go语言的设计思想,string的内容是不可修改的,但是slice元素是可以修改的,基于上述方法得到的string与原来的slice共享底层Buffer,如果不经意修改了slice就可能会造成程序逻辑错误。

标准库与keyword

本节主要分析unsafe包的本质,到底是标准库还是一组keyword。这个思考源于进行指针运算时用到的unsafe.Sizeof,而sizeof在C语言中是个关键字。先从源码入手,梳理unsafe包都提供了些什么,代码如下:

根据源码中的注释,ArbitraryType在这里只是用于文档目的,实际上并不属于unsafe包,它可以表示任意的Go表达式类型。Sizeof()函数用来返回任意类型的大小,Offsetof()函数用来返回任意结构体类型的某个字段在结构体内的偏移,而Alignof()函数用来返回任意类型的对齐边界,最重要的是这3个函数的返回值都是常量。

而Sizeof()函数、Offsetof()函数和Alignof()函数返回的是常量值,也就要求返回值必须在编译阶段确定,所以必须由编译器直接支持。可以通过实验进行验证,代码如下:

这条MOVQ指令直接向返回值o中写入了立即数8,也就说明Sizeof()函数在编译阶段就被转换成了立即数,与C语言中的sizeof并无区别。上述测试方法同样适用于Offsetof()函数和Alignof()函数。

keyword一样,为什么Go语言要放到unsafe包中呢?根本原因还是出于安全考虑。直接的任意操作内存的能力可以让程序员写出更高效的代码,但是也因为过于灵活而让编译器无法落实安全检查,从而使程序变得不安全。unsafe这个名字就旨在提醒程序员,内存操作有风险,要谨慎!

关于uintptr

很多人都认为uintptr是个指针,其实不然。不要对这个名字感到疑惑,它只不过是个uint,大小与当前平台的指针宽度一致。因为unsafe.Pointer可以跟uintptr互相转换,所以Go语言中可以把指针转换为uintptr进行数值运算,然后转换回原类型,以此来模拟C语言中的指针运算。

不要用uintptr来存储堆上对象的地址。具体原因和GC有关,GC在标记对象的时候会跟踪指针类型,而uintptr不属于指针,所以会被GC忽略,造成堆上的对象被认为不可达,进而被释放。用unsafe.Pointer就不会存在这个问题了,unsafe.Pointer类似于C语言中的void∗,虽然未指定元素类型,但是本身类型就是个指针。

内存对齐

硬件的实现一般会将内存的读写对齐到数据总线的宽度,这样既可以降低硬件实现的复杂度,又可以提升传输的效率。

传输的效率。有些硬件平台允许访问未对齐的地址,但是会带来额外的开销,而有的硬件平台不支持访问未对齐的地址,当遇到未对齐的地址时会直接抛出异常。

鉴于这些原因,编译器在定义数据类型时,还有runtime在分配内存时,都要进行对齐操作

Go语言的内存对齐规则参考了两方面因素:

一是数据类型自身的大小,复合类型会参考最大成员大小

二是硬件平台机器字长

机器字长是指计算机进行一次整数运算所能处理的二进制数据的位数。

当数据类型自身大小小于机器字长时,会被对齐到自身大小的整数倍;当自身大小大于机器字长时,会被对齐到机器字长的整数倍。

当数据类型自身大小小于机器字长时,会被对齐到自身大小的整数倍;当自身大小大于机器字长时,会被对齐到机器字长的整数倍。

编译器会按需要在结构体相邻成员之间及最后一个成员之后添加padding,因此需要合理地排列数据成员的顺序,从而使整个struct的空间占用最小化。

数据类型s1在amd64架构上占用了32字节空间。但是这样总共浪费了16字节空间,空间利用率只有50%,因为起始地址也需要对齐,最后与最大数据类型int64对齐。

但是这样总共浪费了16字节空间,空间利用率只有50%。

 

 

经过人为优化成员的顺序后,编译器没有添加任何padding,整个struct占用了16字节空间,利用率达到100%。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值