淺談Golang的unsafe.Pointer

一衰絮、Golang指針與C/C++指針的差別

在Golang支持的數(shù)據(jù)類型中练俐,是包含指針的沸久,但是Golang中的指針季眷,與C/C++的指針卻又不同,筆者覺得主要表現(xiàn)在下面的兩個(gè)方面:

  • 弱化了指針的操作卷胯,在Golang中子刮,指針的作用僅是操作其指向的對象,不能進(jìn)行類似于C/C++的指針運(yùn)算,例如指針相減挺峡、指針移動(dòng)等葵孤。從這一點(diǎn)來看,Golang的指針更類似于C++的引用
  • 指針類型不能進(jìn)行轉(zhuǎn)換橱赠,如int不能轉(zhuǎn)換為int32

上述的兩個(gè)限定主要是為了簡化指針的使用尤仍,減少指針使用過程中出錯(cuò)的機(jī)率,提高代碼的魯棒性狭姨。但是在開發(fā)過程中宰啦,有時(shí)需要打破這些限制,對內(nèi)存進(jìn)行任意的讀寫送挑,這里就需要unsafe.Pointer了绑莺。

二、unsafe.Pointer

1)unsafe.Pointer的定義

從unsate.Pointer的定義如下惕耕,從定義中我們可以看出纺裁,Pointer的本質(zhì)是一個(gè)int的指針:

type ArbitraryType int
type Pointer *ArbitraryType

2)unsafe.Pointer的功能介紹

下面再來看一下Golang官網(wǎng)對于unsafe.Pointer的介紹:

  • 任意類型的指針值都可以轉(zhuǎn)換為unsafe.Pointer(A pointer value of any type can be converted to a Pointer.)
  • unsafe.Pointer可以轉(zhuǎn)換為任意類型的指針值(A Pointer can be converted to a pointer value of any type.)
  • uintptr可以轉(zhuǎn)換為unsafe.Pointer(A uintptr can be converted to a Pointer.)
  • unsafe.Pointer可以轉(zhuǎn)換為uintptr(A Pointer can be converted to a uintptr.)

從上面的功能介紹可以看到,Pointer允許程序突破Golang的類型系統(tǒng)的限制司澎,任意讀寫內(nèi)存欺缘,使用時(shí)需要額外小心,正如它的包名unsafe所提示的一樣挤安。

PS:uintptr本質(zhì)上是一個(gè)用于表示地址值的無符號整數(shù)谚殊,而不是一個(gè)引用,它表示程序中使用的某個(gè)對象的地址值蛤铜。

3)指針類型轉(zhuǎn)換

下面以int64轉(zhuǎn)換為int為例子嫩絮,說明unsafe.Pointer在指針類型轉(zhuǎn)換時(shí)的使用,如下:

func main() {
    i := int64(1)
    var iPtr *int
    // iPtr = &i // 錯(cuò)誤
    iPtr = (*int)(unsafe.Pointer(&i))
    fmt.Printf("%d\n", *iPtr)
}

注意围肥,這種類型轉(zhuǎn)換剿干,需要保證轉(zhuǎn)換后的類型的大小不大于轉(zhuǎn)換前的類型,且具有相同的內(nèi)存布局穆刻,則可將數(shù)據(jù)解釋為另一個(gè)類型置尔。反之,如將int32的指針氢伟,轉(zhuǎn)換為int64的指針榜轿,在后續(xù)的讀寫中,可能會(huì)發(fā)生錯(cuò)誤朵锣。

4)讀寫結(jié)構(gòu)內(nèi)部成員

上面的類型轉(zhuǎn)換只是一個(gè)簡單的例子谬盐,在實(shí)際開發(fā)中,使用unsafe.Pointer進(jìn)行類型轉(zhuǎn)換一般用于讀取結(jié)構(gòu)的私有成員變量或者修改結(jié)構(gòu)的變量猪勇,下面以修改一個(gè)string變量的值為例子设褐,說明類型轉(zhuǎn)換對于任意內(nèi)存讀寫。

我們先來看看在Golang中string是如何定義的:

type stringStruct struct {
    str unsafe.Pointer
    len int
}

string的結(jié)構(gòu)由是由一個(gè)指向字節(jié)數(shù)組的unsafe.Pointer和int類型的長度字段組成,我們可以定義一下與其結(jié)構(gòu)相同的類型助析,并通過unsafe.Pointer把string的指針轉(zhuǎn)換并賦值到新類型的變量中犀被,通過操作該變量來讀寫string內(nèi)部的成員。

在Golang中已經(jīng)存在這樣的結(jié)構(gòu)體了外冀,它就是reflect.StringHeader寡键,它的定義如下:

// StringHeader is the runtime representation of a string.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type StringHeader struct {
    Data uintptr
    Len  int
}

unsafe.Pointer與uintptr在內(nèi)存結(jié)構(gòu)上是相同的,下面通過一個(gè)原地修改字符串的值來演示相關(guān)的操作:

func main() {
    str1 := "hello world"
    hdr1 := (*reflect.StringHeader)(unsafe.Pointer(&str1)) // 注1
    fmt.Printf("str:%s, data addr:%d, len:%d\n", str1, hdr1.Data, hdr1.Len)

    str2 := "abc"
    hdr2 := (*reflect.StringHeader)(unsafe.Pointer(&str2))

    hdr1.Data = hdr2.Data // 注2
    hdr1.Len = hdr2.Len   // 注3
    fmt.Printf("str:%s, data addr:%d, len:%d\n", str1, hdr1.Data, hdr1.Len)
}

其運(yùn)行結(jié)果如下:

str:hello world, data addr:4996513, len:11
str:abc, data addr:4992867, len:3

代碼解釋:

  • 注1:該行代碼是把str1轉(zhuǎn)化為unsafe.Pointer后雪隧,再把unsafe.Pointer轉(zhuǎn)換來StringHeader的指針西轩,然后通過讀寫hdr1的成員即可讀寫str1成員的值
  • 注2:通過修改hdr1的Data的值,修改str1的字節(jié)數(shù)組的指向
  • 注3:為了保證字符串的結(jié)果是完整的脑沿,通過修改hdr1的Len的值藕畔,修改str1的長度字段

最后,str1的值庄拇,已經(jīng)被修改成了str2的值注服,即"abc"。

5)指針運(yùn)算

下面的代碼措近,模擬了通過指針移動(dòng)溶弟,遍歷slice的功能,其本質(zhì)思想是瞭郑,找到slice的第一個(gè)元素的地址辜御,然后通過加上slice每個(gè)元素所占的大小作為偏移量,實(shí)現(xiàn)指針的移動(dòng)和運(yùn)算屈张。

func main() {
    data := []byte("abcd")
    for i := 0; i < len(data); i++ {
        ptr := unsafe.Pointer(uintptr(unsafe.Pointer(&data[0])) + uintptr(i)*unsafe.Sizeof(data[0])) 
        fmt.Printf("%c,", *(*byte)(unsafe.Pointer(ptr)))
    }
    fmt.Printf("\n")
}

其運(yùn)行結(jié)果如下:

a,b,c,d,

代碼解釋:

要理解上述代碼擒权,首選需要了解兩個(gè)原則,分別是:

  • 其他類型的指針只能轉(zhuǎn)化為unsafe.Pointer阁谆,也只有unsafe.Pointer才能轉(zhuǎn)化成任意類型的指針
  • 只有uintptr才支持加減操作菜拓,而uintptr是一個(gè)非負(fù)整數(shù),表示地址值笛厦,沒有類型信息,以字節(jié)為單位

for循環(huán)的ptr賦值是該例子中的重點(diǎn)代碼俺夕,它表示:

  1. 把data的第0個(gè)元素的地址裳凸,轉(zhuǎn)化為unsafe.Pointer,再把它轉(zhuǎn)換成uintptr劝贸,用于加減運(yùn)算姨谷,即(uintptr(unsafe.Pointer(&data[0])) )
  2. 加上第i個(gè)元素的偏移量,得到一個(gè)新的uintptr值映九,計(jì)算方法為i每個(gè)元素所占的字節(jié)數(shù)梦湘,即(+ uintptr(i)unsafe.Sizeof(data[0]))
  3. 把新的uintptr再轉(zhuǎn)化為unsafe.Pointer,用于在后續(xù)的打印操作中,轉(zhuǎn)化為實(shí)際類型的指針

三捌议、總結(jié)

閱讀本文后哼拔,希望能讓你對unsafe.Pointer有一定的了解,總的來說瓣颅,它的作用是用于打破類型系統(tǒng)實(shí)現(xiàn)更靈活的內(nèi)存讀寫倦逐。但同時(shí)也是不安全的,使用時(shí)需要額外小心宫补。

總結(jié)一下unsafe.Pointer的使用法則就是:

  • 任意類型的指針值都可以轉(zhuǎn)換為unsafe.Pointer檬姥,unsafe.Pointer也可以轉(zhuǎn)換為任意類型的指針值
  • unsafe.Pointer與uintptr可以實(shí)現(xiàn)相互轉(zhuǎn)換
  • 可以通過uintptr可以進(jìn)行加減操作,從而實(shí)現(xiàn)指針的運(yùn)算
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末粉怕,一起剝皮案震驚了整個(gè)濱河市健民,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贫贝,老刑警劉巖秉犹,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異平酿,居然都是意外死亡凤优,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門蜈彼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筑辨,“玉大人,你說我怎么就攤上這事幸逆」髟” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵还绘,是天一觀的道長楚昭。 經(jīng)常有香客問我,道長拍顷,這世上最難降的妖魔是什么抚太? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮昔案,結(jié)果婚禮上尿贫,老公的妹妹穿的比我還像新娘。我一直安慰自己踏揣,他們只是感情好庆亡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著捞稿,像睡著了一般又谋。 火紅的嫁衣襯著肌膚如雪拼缝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天彰亥,我揣著相機(jī)與錄音咧七,去河邊找鬼。 笑死剩愧,一個(gè)胖子當(dāng)著我的面吹牛猪叙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仁卷,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼穴翩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了锦积?” 一聲冷哼從身側(cè)響起芒帕,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎丰介,沒想到半個(gè)月后背蟆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哮幢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年带膀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片橙垢。...
    茶點(diǎn)故事閱讀 39,932評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡垛叨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出柜某,到底是詐尸還是另有隱情嗽元,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布喂击,位于F島的核電站剂癌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏翰绊。R本人自食惡果不足惜佩谷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望监嗜。 院中可真熱鬧琳要,春花似錦、人聲如沸秤茅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽框喳。三九已至课幕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間五垮,已是汗流浹背乍惊。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留放仗,地道東北人润绎。 一個(gè)月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像诞挨,于是被迫代替她去往敵國和親莉撇。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評論 2 354