21

图解Go的unsafe.Pointer

 4 years ago
source link: https://i6448038.github.io/2020/03/13/unsafe-pointer/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

相信看过Go源码的同学已经对 unsafe.Pointer 非常的眼熟,因为这个类型可以说在源码中是随处可见: mapchannelinterfaceslice …但凡你能想到的内容,基本都会有 unsafe.Pointer 的影子。

看字面意思, unsafe.Pointer 是“不安全的指针”,指针就指针吧,还安不安全的是个什么鬼?

接下来,我们就来了解一下Go的这个“不安全的指针” unsafe.Pointer

什么叫变量

在了解指针之前,我们有必要先了解一下什么叫“变量”。

其实变量就是一个内存地址的名字,这听起来可能有些奇怪:指针不是地址码?

听我细细来讲:此地址非彼地址。通常,我们要在计算机内存中存数据,我们会怎么做?

我们肯定说:“计算机,在0x0201地址内存一个数100”。就这一句话,别看它糙,实际上在计算机中真就这么干的。然后我们接着说:“在0x0202中存什么,在0x0203中存什么,把0x0203中的值变为0x0201中的值…”

这些“0x0201”、“0x0202”、“0x0203”…这些数字儿是不是不太好记?写个代码是不是头都大了?

于是聪明的先人给想了个办法,把这些地址换成 代号 ,“0x0201”我叫x,“0x0202”我给他起个名字叫y,“0x0203”我给他起个名字叫z…

于是 “计算机,在0x0201地址内存一个数100”。就变成了 var x int =100

而这个这个代号就是变量。

0x0201地址    =============》   100

0x0201地址    ======》X ===》   100

果然,计算机界中的任何问题,都可以通过加一个中间层来解决。(#^.^#)

最后,计算机会在内存中存代号和变量地址的对应关系。

什么叫指针

我们印象中的指针这个概念,其实就是一个存了内存地址的对象,这个对象指向的内存地址可能是另外一个对象、函数或者结构体等。

ZN3eUzv.png!web

这个理解没错,但是一定要理清楚指针和变量的关系。

6BBBv2A.jpg!web

在一般的指针中,由于指针只是一个地址,底层实现是一个unsigned int,所以在C语言中,指针之间的赋值和计算等同类型之间的操作很常见。

以下代码扫一眼,看看是否知道输出结果。

#include "stdio.h"



int main(int argc, char const *argv[])
{
    char c = 'b';
    int i = 1000;
    char *cp;
    int *ip;
    
    //指针的正常赋值
    cp = &c;
    ip = &i;
    printf("cp[%p]\n", cp); //cp[0x7ffee904275f]
    printf("ip[%p]\n", ip); //ip[0x7ffee9042758]


    //指针的计算
    cp = cp + 1;
    ip = ip + 1;
    printf("cp[%p]\n", cp); //cp[0x7ffee9042760]
    printf("ip[%p]\n", ip); //ip[0x7ffee904275c]


    //不同"类型"指针之间的赋值
    cp = ip;
    printf("cp[%p] ip[%p]\n", cp, ip); //cp[0x7ffee904275c] ip[0x7ffee904275c]


    //不同指针之间的比较  输出true
    if (cp == ip) {
        printf("true\n");
    } else {
        printf("false\n");
    }
}

通常意义上我们了解的不同类型的指针,可以归为“同一类型”,无论是int类型的指针还是char类型的指针,都称之为“指针类型”。

指针指向对象类型的约束对指针本身而言非常弱,因为在通常C语言中的定义不同类型的指针,只是为了调用的方便。例如一个指针指向了某一个结构体,那么我写代码的时候就可以方便的使用该结构体的属性字段;可以说通常意义上的C指针,是个“万能类型”的,啥类型的指针都和 void* 一样,万能!

所以,在C语言中,假如不使用指针,可以认为是机器在帮我们“打理”内存。

2QJjqiY.png!web

但是假如我们使用了指针,由于指针的自由度非常大,我们就可以自己“打理”内存了(PS:这里的打理仅限内存指向问题,分配和清除肯定必然不行)。

32uqyqF.png!web

Go中常用的指针

在C语言中,指针的操作是完全不被约束的,这就非常的危险:程序猿在写的时候就得细心一点,拿着指针操作太多,指来指去,指到不该指的地方,就不好了~

所以Go语言在设计的时候,也考虑到了这个问题,就给现有的指针加了约束:把指针类型的数据当成了一种数据类型,在编译的时候做严格判断。

举个例子来说: *int*string 是两种不同的类型,那既然类型都不同,那么 *int 的数据就不能够和 *string 类型的数据进行“互通”了,既不能相互赋值,也不能相互比较;

能不能加减运算呀?当然不能,因为“数字儿”是整型, *int 或者 *string 是其他类型而非整型。

Go语言就给指针套了个“类型”的帽子,一下子把指针限制的死死的了。

而且Go最后规定:指针类型还不能相互强制转换。

我们知道:int和string是可以相互转换的,既然指针归根到底是地址,也就是数字儿,那指针类型和int之间能否相互强制类型转换呢?答案是不行!

*int*string 之间是否可以强制类型转换呢?答案是更不行,如果能强制转换了,前面说的给指针套的那顶“类型”的帽子,是不是就白做了?

unsafe.Pointer

好了,扯了那么多,终于到正题了。那么unsafe.Pointer指针是什么呢?

其实就一句话:就是C语言中那个牛逼到爆的什么都能指的不安全的指针。再确切一点是: void*

unsafe.Pointer 源码就两行:

type ArbitraryType int //表示任何类型
type Pointer *ArbitraryType //表示任何类型的指针

unsafe.Pointer 的源码注释还提供了关于 unsafe.Pointer 的四点重要的使用规则:

1、Go语言常规的任何类型的指针都可以转化为unsafe.Pointer类型
2、unsafe.Pointer类型可以转化为Go语言常规的任何类型的指针。
3、uintptr这个类型可以转化为unsafe.Pointer
4、unsafe.Pointer可以转化为uintptr

看完规则,你可能会问: uintptr 是啥?

来,没有比源码更好的解释的了:

jmyuIjm.png!web

注意看 uintptr 的位置,和 int 以及 uint 在一个包内,你可以认为 uintptr 与它们”同类”,只不过是指针的专属而已,但是你想自己定义用也能用。

对于 unsafe.Pointer ,多用于Go的编译时期;由于它可以绕过类型系统,直接去访问内存,所以它用起来效率会比较高,但是官方的态度是不太建议使用的,因为不太安全。我个人建议也是能不用就不用:毕竟为了这点儿效率带来的额外的附加成本比较高。

好了,我们最后总结一下Go的指针:

iMZjEvJ.png!web

更多精彩内容,请关注我的微信公众号 互联网技术窝

z6V3iiJ.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK