图解Go的unsafe.Pointer

相信看过Go源码的同窗已经对unsafe.Pointer很是的眼熟,由于这个类型能够说在源码中是随处可见:mapchannelinterfaceslice...但凡你能想到的内容,基本都会有unsafe.Pointer的影子。安全

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

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

什么叫变量

在了解指针以前,咱们有必要先了解一下什么叫“变量”。 其实变量就是一个内存地址的名字,这听起来可能有些奇怪:指针不是地址码? 听我细细来说:此地址非彼地址。一般,咱们要在计算机内存中存数据,咱们会怎么作? 咱们确定说:“计算机,在0x0201地址内存一个数100”。就这一句话,别看它糙,实际上在计算机中真就这么干的。而后咱们接着说:“在0x0202中存什么,在0x0203中存什么,把0x0203中的值变为0x0201中的值…”markdown

这些“0x0201”、“0x0202”、“0x0203”…这些数字儿是否是不太好记?写个代码是否是头都大了? 因而聪明的先人给想了个办法,把这些地址换成代号,“0x0201”我叫x,“0x0202”我给他起个名字叫y,“0x0203”我给他起个名字叫z…函数

因而 “计算机,在0x0201地址内存一个数100”。就变成了var x int =100。 而这个这个代号就是变量。ui

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

0x0201地址    ======》X ===》   100
复制代码

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

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

什么叫指针

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

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

在通常的指针中,因为指针只是一个地址,底层实现是一个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语言中,假如不使用指针,能够认为是机器在帮咱们“打理”内存。

可是假如咱们使用了指针,因为指针的自由度很是大,咱们就能够本身“打理”内存了(PS:这里的打理仅限内存指向问题,分配和清除确定必然不行)。

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的四点重要的使用规则:

一、Go语言常规的任何类型的指针均可以转化为unsafe.Pointer类型
二、unsafe.Pointer类型能够转化为Go语言常规的任何类型的指针。
三、uintptr这个类型能够转化为unsafe.Pointer
四、unsafe.Pointer能够转化为uintptr
复制代码

看完规则,你可能会问:uintptr是啥? 来,没有比源码更好的解释的了:

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

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

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

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

相关文章
相关标签/搜索