圖解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的指針:

更多精彩內容,請關注個人微信公衆號 互聯網技術窩

相關文章
相關標籤/搜索