golang中的unsafe包詳解

一春缕、unsafe 作用

從golang的定義來看,unsafe 是類型安全的操作艘蹋。顧名思義锄贼,它應(yīng)該非常謹(jǐn)慎地使用; unsafe可能很危險(xiǎn),但也可能非常有用女阀。例如宅荤,當(dāng)使用系統(tǒng)調(diào)用和Go結(jié)構(gòu)必須具有與C結(jié)構(gòu)相同的內(nèi)存布局時(shí),您可能別無選擇浸策,只能使用unsafe冯键。關(guān)于指針操作,在unsafe包官方定義里有四個(gè)描述:

  1. 任何類型的指針都可以被轉(zhuǎn)化為Pointer
  2. Pointer可以被轉(zhuǎn)化為任何類型的指針
  3. uintptr可以被轉(zhuǎn)化為Pointer
  4. Pointer可以被轉(zhuǎn)化為uintptr

額外在加上一個(gè)規(guī)則:指向不同類型數(shù)據(jù)的指針庸汗,是無法直接相互轉(zhuǎn)換的惫确,必須借助unsafe.Pointer(類似于C的 void指針)代理一下再轉(zhuǎn)換也就是利用上述的1,2規(guī)則。
舉例:

func Float64bits(f float64) uint64 {
  // 無法直接轉(zhuǎn)換改化,報(bào)錯(cuò):Connot convert expression of type *float64 to type *uint64
  // return *(*uint64)(&f)   

 // 先把*float64 轉(zhuǎn)成 Pointer(描述1)掩蛤,再把Pointer轉(zhuǎn)成*uint64(描述2)
  return *(*uint64)(unsafe.Pointer(&f)) 
}

二、unsafe的定義:

整體代碼比較簡單陈肛,2個(gè)類型定義和3個(gè)uintptr的返回函數(shù)

package unsafe
//ArbitraryType僅用于文檔目的揍鸟,實(shí)際上并不是unsafe包的一部分,它表示任意Go表達(dá)式的類型。
type ArbitraryType int
//任意類型的指針燥爷,類似于C的*void
type Pointer *ArbitraryType
//確定結(jié)構(gòu)在內(nèi)存中占用的確切大小
func Sizeof(x ArbitraryType) uintptr
//返回結(jié)構(gòu)體中某個(gè)field的偏移量
func Offsetof(x ArbitraryType) uintptr
//返回結(jié)構(gòu)體中某個(gè)field的對(duì)其值(字節(jié)對(duì)齊的原因)
func Alignof(x ArbitraryType) uintptr

看一個(gè)栗子:

package main

import (
    "fmt"
    "unsafe"
)

type Human struct {
    sex  bool
    age  uint8
    min  int
    name string
}

func main() {
    h := Human{
        true,
        30,
        1,
        "hello",
    }
    i := unsafe.Sizeof(h)
    j := unsafe.Alignof(h.age
)
    k := unsafe.Offsetof(h.name)
    fmt.Println(i, j, k)
    fmt.Printf("%p\n", &h)
    var p unsafe.Pointer
    p = unsafe.Pointer(&h)
    fmt.Println(p)
}

//輸出
//32 1 16
//0xc00000a080
//0xc00000a080
// 32:string 占16字節(jié)蜈亩,所以16+16 =32;1 是因?yàn)閍ge前是bool前翎,占用1個(gè)字節(jié)稚配;8是name的偏移是int 占8個(gè)字
節(jié)

三、Pointer使用

前面已經(jīng)說了港华,pointer是任意類型的指針道川,可以指向任意類型數(shù)據(jù)。參照Float64bits的轉(zhuǎn)換和上述栗子的unsafe.Pointer(&h)立宜,所以主要用于轉(zhuǎn)換各種類型

四冒萄、uintptr

在golang中uintptr的定義是 type uintptr uintptr uintptr是golang的內(nèi)置類型,是能存儲(chǔ)指針的整型

  1. 根據(jù)描述3橙数,一個(gè)unsafe.Pointer指針也可以被轉(zhuǎn)化為uintptr類型尊流,然后保存到指針型數(shù)值變量中(注:這只是和當(dāng)前指針相同的一個(gè)數(shù)字值,并不是一個(gè)指針)灯帮,然后用以做必要的指針數(shù)值運(yùn)算崖技。(uintptr是一個(gè)無符號(hào)的整型數(shù),足以保存一個(gè)地址)
  2. 這種轉(zhuǎn)換雖然也是可逆的钟哥,但是將uintptr轉(zhuǎn)為unsafe.Pointer指針可能會(huì)破壞類型系統(tǒng)迎献,因?yàn)椴⒉皇撬械臄?shù)字都是有效的內(nèi)存地址。
  3. 許多將unsafe.Pointer指針轉(zhuǎn)為uintptr腻贰,然后再轉(zhuǎn)回為unsafe.Pointer類型指針的操作也是不安全的吁恍。比如下面的例子需要將變量x的地址加上b字段地址偏移量轉(zhuǎn)化為*int16類型指針,然后通過該指針更新x.b:
package main

import (
    "fmt"
    "unsafe"
)

func main() {

    var x struct {
        a bool
        b int16
        c []int
    }

    /**
    unsafe.Offsetof 函數(shù)的參數(shù)必須是一個(gè)字段 x.f, 然后返回 f 字段相對(duì)于 x 起始地址的偏移量, 包括可能的空洞.
    */

    /**
    uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
    指針的運(yùn)算
    */
    // 和 pb := &x.b 等價(jià)
    pb := (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
    *pb = 42
    fmt.Println(x.b) // "42"
}

上面的寫法盡管很繁瑣播演,但在這里并不是一件壞事冀瓦,因?yàn)檫@些功能應(yīng)該很謹(jǐn)慎地使用。不要試圖引入一個(gè)uintptr類型的臨時(shí)變量写烤,因?yàn)樗赡軙?huì)破壞代碼的安全性(注:這是真正可以體會(huì)unsafe包為何不安全的例子)翼闽。

下面段代碼是錯(cuò)誤的:

// NOTE: subtly incorrect!
tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
pb := (*int16)(unsafe.Pointer(tmp))
*pb = 42

產(chǎn)生錯(cuò)誤的原因很微妙。

有時(shí)候垃圾回收器會(huì)移動(dòng)一些變量以降低內(nèi)存碎片等問題顶霞。這類垃圾回收器被稱為移動(dòng)GC肄程。當(dāng)一個(gè)變量被移動(dòng)锣吼,所有的保存改變量舊地址的指針必須同時(shí)被更新為變量移動(dòng)后的新地址。從垃圾收集器的視角來看蓝厌,一個(gè)unsafe.Pointer是一個(gè)指向變量的指針玄叠,因此當(dāng)變量被移動(dòng)是對(duì)應(yīng)的指針也必須被更新;但是uintptr類型的臨時(shí)變量只是一個(gè)普通的數(shù)字拓提,所以其值不應(yīng)該被改變读恃。上面錯(cuò)誤的代碼因?yàn)橐胍粋€(gè)非指針的臨時(shí)變量tmp,導(dǎo)致垃圾收集器無法正確識(shí)別這個(gè)是一個(gè)指向變量x的指針代态。當(dāng)?shù)诙€(gè)語句執(zhí)行時(shí)寺惫,變量x可能已經(jīng)被轉(zhuǎn)移,這時(shí)候臨時(shí)變量tmp也就不再是現(xiàn)在的&x.b地址蹦疑。第三個(gè)向之前無效地址空間的賦值語句將徹底摧毀整個(gè)程序西雀!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市歉摧,隨后出現(xiàn)的幾起案子艇肴,更是在濱河造成了極大的恐慌,老刑警劉巖叁温,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件再悼,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡膝但,警方通過查閱死者的電腦和手機(jī)冲九,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跟束,“玉大人莺奸,你說我怎么就攤上這事∮韭” “怎么了憾筏?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵嚎杨,是天一觀的道長花鹅。 經(jīng)常有香客問我,道長枫浙,這世上最難降的妖魔是什么刨肃? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮箩帚,結(jié)果婚禮上真友,老公的妹妹穿的比我還像新娘。我一直安慰自己紧帕,他們只是感情好盔然,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布桅打。 她就那樣靜靜地躺著,像睡著了一般愈案。 火紅的嫁衣襯著肌膚如雪挺尾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天站绪,我揣著相機(jī)與錄音遭铺,去河邊找鬼。 笑死恢准,一個(gè)胖子當(dāng)著我的面吹牛魂挂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播馁筐,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼涂召,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了敏沉?” 一聲冷哼從身側(cè)響起芹扭,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赦抖,沒想到半個(gè)月后舱卡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡队萤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年轮锥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片要尔。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡舍杜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赵辕,到底是詐尸還是另有隱情既绩,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布还惠,位于F島的核電站饲握,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蚕键。R本人自食惡果不足惜救欧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锣光。 院中可真熱鬧笆怠,春花似錦、人聲如沸誊爹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至办成,卻和暖如春泊柬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诈火。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國打工兽赁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冷守。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓刀崖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拍摇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子亮钦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353