《Go语言实战》读书笔记,未完待续,欢迎扫码关注公众号
flysnow_org
或者网站http://www.flysnow.org/,第一时间看后续笔记。觉得有帮助的话,顺手分享到朋友圈吧,感谢支持。
Go语言在设计的时候,为了编写方便、效率高以及降低复杂度,被设计成为一门强类型的静态语言。强类型意味着一旦定义了,它的类型就不能改变了;静态意味着类型检查在运行前就做了。
同时为了安全的考虑,Go语言是允许两个指针类型进行转换的。
指针类型转换
我们一般使用*T
作为一个指针类型,表示一个指向类型T变量的指针。为了安全的考虑,两个不同的指针类型不能相互转换,比如*int
不能转为*float64
。
|
|
以上代码我们在编译的时候,会提示cannot convert ip (type *int) to type *float64
,也就是不能进行强制转型。那如果我们还是需要进行转换怎么做呢?这就需要我们使用unsafe包里的Pointer
了,下面我们先看看unsafe.Pointer
是什么,然后再介绍如何转换。
Pointer
unsafe.Pointer
是一种特殊意义的指针,它可以包含任意类型的地址,有点类似于C语言里的void*指针,全能型的。
|
|
以上示例,我们可以把*int
转为*float64
,并且我们尝试了对新的*float64
进行操作,打印输出i
,就会发现i
的址同样被改变。
以上这个例子没有任何实际的意义,但是我们说明了,通过unsafe.Pointer
这个万能的指针,我们可以在*T
之间做任何转换。
|
|
可以看到unsafe.Pointer
其实就是一个*int
,一个通用型的指针。
我们看下关于unsafe.Pointer
的4个规则。
- 任何指针都可以转换为
unsafe.Pointer
unsafe.Pointer
可以转换为任何指针uintptr
可以转换为unsafe.Pointer
unsafe.Pointer
可以转换为uintptr
前面两个规则我们刚刚已经演示了,主要用于*T1
和*T2
之间的转换,那么最后两个规则是做什么的呢?我们都知道*T
是不能计算偏移量的,也不能进行计算,但是uintptr
可以,所以我们可以把指针转为uintptr
再进行便宜计算,这样我们就可以访问特定的内存了,达到对不同的内存读写的目的。
下面我们以通过指针偏移修改Struct结构体内的字段为例,来演示uintptr
的用法。
|
|
以上我们通过内存偏移的方式,定位到我们需要操作的字段,然后改变他们的值。
第一个修改user
的name
值的时候,因为name
是第一个字段,所以不用偏移,我们获取user
的指针,然后通过unsafe.Pointer
转为*string
进行赋值操作即可。
第二个修改user
的age
值的时候,因为age
不是第一个字段,所以我们需要内存偏移,内存偏移牵涉到的计算只能通过uintptr
,所我们要先把user
的指针地址转为uintptr
,然后我们再通过unsafe.Offsetof(u.age)
获取需要偏移的值,进行地址运算(+)偏移即可。
现在偏移后,地址已经是user
的age
字段了,如果要给它赋值,我们需要把uintptr
转为*int
才可以。所以我们通过把uintptr
转为unsafe.Pointer
,再转为*int
就可以操作了。
这里我们可以看到,我们第二个偏移的表达式非常长,但是也千万不要把他们分段,不能像下面这样。
|
|
逻辑上看,以上代码不会有什么问题,但是这里会牵涉到GC,如果我们的这些临时变量被GC,那么导致的内存操作就错了,我们最终操作的,就不知道是哪块内存了,会引起莫名其妙的问题。
小结
unsafe是不安全的,所以我们应该尽可能少的使用它,比如内存的操纵,这是绕过Go本身设计的安全机制的,不当的操作,可能会破坏一块内存,而且这种问题非常不好定位。
当然必须的时候我们可以使用它,比如底层类型相同的数组之间的转换;比如使用sync/atomic包中的一些函数时;还有访问Struct的私有字段时;该用还是要用,不过一定要慎之又慎。
还有,整个unsafe包都是用于Go编译器的,不用运行时,在我们编译的时候,Go编译器已经把他们都处理了。
《Go语言实战》读书笔记,未完待续,欢迎扫码关注公众号
flysnow_org
或者网站http://www.flysnow.org/,第一时间看后续笔记。觉得有帮助的话,顺手分享到朋友圈吧,感谢支持。
有疑问加站长微信联系(非本文作者)