欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

GoLang unsafe包詳細講解

 更新時間:2022年10月12日 08:34:45   作者:~龐貝  
從golang的定義來看,unsafe 是類型安全的操作。顧名思義,它應(yīng)該非常謹慎地使用; unsafe可能很危險,但也可能非常有用。例如,當(dāng)使用系統(tǒng)調(diào)用和Go結(jié)構(gòu)必須具有與C結(jié)構(gòu)相同的內(nèi)存布局時,您可能別無選擇,只能使用unsafe

1.前言

開發(fā)中,[]byte類型和string類型需要互相轉(zhuǎn)換的場景并不少見,直接的想法是像下面這樣進行強制類型轉(zhuǎn)換:

    a := "Kylin Lab"
	b := []byte(a)
	fmt.Println(a)//Kylin Lab
	fmt.Println(b)//[75 121 108 105 110 32 76 97 98]

如果接下來需要對b進行修改,那么這樣轉(zhuǎn)換就沒什么問題,但是如果只是因為類型不合適,并不需要對轉(zhuǎn)換后的變量做任何修改,那這樣轉(zhuǎn)換就顯得不劃算了。我們知道,[]byte和string的內(nèi)存布局如下圖所示:

可以看到它們都有一個底層數(shù)組來存儲變量數(shù)據(jù),而類型本身只記錄這個數(shù)組的起始地址。如果采用強制類型轉(zhuǎn)換的方式把a轉(zhuǎn)換為b,那么就會重新分配b使用的底層數(shù)組。然后把a的底層數(shù)組內(nèi)容拷貝到b的底層數(shù)組。如果字符串內(nèi)容很多,多占用這許多字節(jié)的內(nèi)存不說,還要耗費時間做拷貝,所以就顯得很不合適了。

要是可以讓b重復(fù)使用a的底層數(shù)組,那就好了。強轉(zhuǎn)不行,就到了unsafe上場的時候了~

2.指針類型轉(zhuǎn)換

unsafe提供的第一件法寶就是指針類型轉(zhuǎn)換。我們知道像下面這樣的指針類型轉(zhuǎn)換是編譯不通過的。

a := "Kylin Lab"
var b []byte
tmp := (*string)(&b)
//cannot convert &b (type *[]byte) to type *string

但是你可以把任意一個指針類型轉(zhuǎn)換為unsafe.Pointer類型,再把unsafe.Pointer類型轉(zhuǎn)換為任意指針類型,就像下面這樣是可以正常執(zhí)行的:

tmp := (*string)(unsafe.Pointer(&b))

現(xiàn)在我們通過unsafe.Pointer把b的指針轉(zhuǎn)換為*string類型,我們可以放心的這樣做,是因為我們知道slice的底層布局與string是兼容的,b的前兩項內(nèi)容與a相同,都是一個uintptr和一個int??蓞⒁妑eflect包中關(guān)于這兩個類型的定義:

//reflect/value.go
type StringHeader struct {
    Data uintptr
    Len  int
}
type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

我們知道上面這個例子中 變量b只初始化了變量結(jié)構(gòu),并未初始化底層數(shù)組,元素個數(shù)和容量都為0。

接下來,我們把a賦值給tmp:

    a := "Kylin Lab"
	var b []byte
	tmp := (*string)(unsafe.Pointer(&b))
	*tmp = a
	fmt.Println(a)         //Kylin Lab
	fmt.Println(b)         //[75 121 108 105 110 32 76 97 98]
	fmt.Println(*tmp)      //Kylin Lab
	fmt.Println(tmp)       //0xc000004078
	fmt.Printf("%p\n", &a) //0xc00005a250
	fmt.Printf("%p\n", &b) //0xc000004078
	fmt.Println(&a)        //0xc00005a250
	fmt.Println(&b)//&[75 121 108 105 110 32 76 97 98]

現(xiàn)在你猜怎么著,我們已經(jīng)在變量b中重復(fù)使用了a的底層數(shù)組,元素個數(shù)也填好了~

不過還沒完,b的容量還為0呢!怎么修改它呢?我們能拿到b的地址,也知道data和len各占8字節(jié)(64位下),只要把b的指針加上16字節(jié)就是cap的起始地址。可問題是Go語言的指針支持做加減運算嗎?不支持!

這時候就要拿出unsafe提供的第二件法寶了!

    a := "Kylin Lab"
	var b []byte
	tmp := (*string)(unsafe.Pointer(&b))
	*tmp = a
	fmt.Println(len(a)) //9
	fmt.Println(len(b)) //9
	fmt.Println(cap(b)) //0
//unsafe/unsafe.go
package unsafe
type ArbitraryType int
type IntegerType int//引用不會出錯
type Pointer *ArbitraryType
func Sizeof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr
func Add(ptr Pointer, len IntegerType) Pointer
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
//builtin/builtin.go
// uintptr is an integer type that is large enough to hold the bit pattern of
// any pointer.
type uintptr uintptr
// IntegerType is here for the purposes of documentation only. It is a stand-in
// for any integer type: int, uint, int8 etc.
type IntegerType int//引用會出錯

3.指針運算

Go語言不支持指針直接進行運算,也是為了保障程序運行安全,防止出現(xiàn)莫名其妙的、玄之又玄的bug。

不過unsafe.Pointer可以和各種指針類型相互轉(zhuǎn)換,也可以轉(zhuǎn)換為uintptr類型,uintptr本質(zhì)上就是一個無符號整型,所以它是可以進行運算的。 繼續(xù)上面的例子,我們可以把b的指針轉(zhuǎn)換為unsafe.Pointer,再進一步轉(zhuǎn)換為uintptr。

(uintptr)(unsafe.Pointer(&b))

現(xiàn)在就把b的地址轉(zhuǎn)換為uintptr類型了,64位下,如果把它加上16,就是b的容量的起始地址了。

(uintptr)(unsafe.Pointer(&b)) + 16

即便如此,我們也不能直接通過uintptr來修改b的容量,因為它不是指針類型,而且也不能直接轉(zhuǎn)換為指針類型。但是可以通過unsafe.Pointer類型中轉(zhuǎn)一下。

tmp2 := (*int)(unsafe.Pointer((uintptr)(unsafe.Pointer(&b)) + 16))

現(xiàn)在才算是拿到了b的容量的指針,再通過這個*int修改b的容量就OK了~

*tmp2 = len(b)

目前為止,我們已經(jīng)借助unsafe的兩個法寶,成功完成了string到[]byte的轉(zhuǎn)換,并且復(fù)用了a的底層數(shù)組。

    a := "Kylin Lab"
	var b []byte
	tmp := (*string)(unsafe.Pointer(&b))
	*tmp = a
	tmp2 := (*int)(unsafe.Pointer((uintptr)(unsafe.Pointer(&b)) + 16))
	*tmp2 = len(b)
	fmt.Println(len(a)) //9
	fmt.Println(len(b)) //9
	fmt.Println(cap(b)) //9

上面tmp2賦值這一行很長,也很繞。

注:雖然下面可以編譯過,但是一定不要像下面這樣先使用uintptr類型的臨時變量來存儲一個地址,然后才把它轉(zhuǎn)換為某個指針類型。

tmp2 := (uintptr)(unsafe.Pointer(&b)) + 16
capPtr := (*int)(unsafe.Pointer(tmp2))

這是因為uintptr只是一個存儲著地址的無符號整型而已,它不是指針,如果垃圾回收為了減少內(nèi)存碎片而移動了一些變量,內(nèi)存關(guān)聯(lián)到的指針類型的值是會一并修改的,但是uintptr并不會,這就可能出現(xiàn)一些神奇的bug,所以這一行只能這么繞著寫。

除此之外,這個硬編碼的“16”怎么看都顯得格外不和諧。有沒有什么好方法,可以獲取程序運行平臺中一個類型的大小呢?這就要用到unsafe提供的第三個法寶了~

4.獲取大小和偏移

unsafe.Sizeof可以拿到任意類型的大小,unsafe.Alignof可以拿到任意類型的對齊邊界。按照reflect.SliceHeader的定義,我們這里可以用unsafe.Sizeof來獲取uintptr和int的大小,b的起始地址偏移這么多就是第三個字段Cap的地址了。

a := "Kylin Lab"
var b []byte
tmp := (*string)(unsafe.Pointer(&b))
*tmp = a
tmp2 := (*int)(unsafe.Pointer((uintptr)(unsafe.Pointer(&b)) + unsafe.Sizeof(uintptr(1)) + unsafe.Sizeof(1)))
*tmp2 = len(b)
fmt.Println(len(a)) //9
fmt.Println(len(b)) //9
fmt.Println(cap(b)) //9

不過這樣還是存在投機的成分,別忘了內(nèi)存對齊哦~

這里這樣寫可行,是因為我們知道uintptr和int的大小不是4字節(jié)就是8字節(jié),無論哪一種,都會緊挨著第三個字段,不會出現(xiàn)因內(nèi)存對齊而形成的間隙。

所以unsafe還有一個unsafe.Offsetof方法可以獲得結(jié)構(gòu)體中某個字段距離結(jié)構(gòu)體起始地址的偏移值,這樣就可以確定結(jié)構(gòu)體成員正確的位置了。

為了試試這個方法,我們要把b的指針轉(zhuǎn)換為reflect.SliceHeader類型,其實也可以自己定義一個SliceHeader類型,但這不是有現(xiàn)成的可以直接拿來用嘛~

bPtr := (*reflect.SliceHeader)(unsafe.Pointer(&b)) 

然后獲取Cap字段在結(jié)構(gòu)體內(nèi)的偏移值:

unsafe.Offsetof(bPtr.Cap)

再然后,就是把這個字段的地址轉(zhuǎn)換為*int,然后修改它的值了:

    a := "Kylin Lab"
	var b []byte
	tmp := (*string)(unsafe.Pointer(&b))
	*tmp = a
	bPtr := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	tmp2 := (*int)(unsafe.Pointer((uintptr)(unsafe.Pointer(&b)) + unsafe.Offsetof(bPtr.Cap)))
	*tmp2 = len(b)
	fmt.Println(len(a)) //9
	fmt.Println(len(b)) //9
	fmt.Println(cap(b)) //9

我們?yōu)榱硕嘟榻B一些unsafe的功能,刻意繞了個遠~

其實都把b轉(zhuǎn)換為reflect.SliceHeader結(jié)構(gòu)體了,改個字段值哪里要這么麻煩!??!我們大可以這樣做:

strHeader := (*reflect.StringHeader)(unsafe.Pointer(&a))
sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b))

這樣通過strHeader和sliceHeader想操作哪個字段都很方便。

    a := "Kylin Lab"
	var b []byte
	strHeader := (*reflect.StringHeader)(unsafe.Pointer(&a))
	sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	sliceHeader.Data = strHeader.Data
	sliceHeader.Len = strHeader.Len
	sliceHeader.Cap = strHeader.Len
	fmt.Println(len(a)) //9
	fmt.Println(len(b)) //9
	fmt.Println(cap(b)) //9

5.關(guān)于string

關(guān)于string,我們還要啰嗦一點,Go語言中string變量的內(nèi)容默認是不會被修改的,而我們通過給string變量整體賦新值的方式來改變它的內(nèi)容時,實際上會重新分配它的底層數(shù)組。

而string類型字面量的底層數(shù)組會被分配到只讀數(shù)據(jù)段,在我們的例子中,b復(fù)用了a的底層數(shù)組,所以就不能再像下面這樣修改b的內(nèi)容了,否則執(zhí)行階段會發(fā)生錯誤。

    a := "Kylin Lab"
	var b []byte
	strHeader := (*reflect.StringHeader)(unsafe.Pointer(&a))
	sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	sliceHeader.Data = strHeader.Data
	sliceHeader.Len = strHeader.Len
	sliceHeader.Cap = strHeader.Len
	b[0] = 'k'
	/*運行報錯:
              unexpected fault address 0x6d1875                     
              fatal error: fault                                    
              [signal 0xc0000005 code=0x1 addr=0x6d1875 pc=0x6c013a]*/

而運行時動態(tài)拼接而成的string變量,它的底層數(shù)組不在只讀數(shù)據(jù)段,而是由Go語言在語法層面阻止對字符串內(nèi)容的修改行為。

a := "Kylin Lab"  //string字面量
c := "Hello " + a //動態(tài)拼接的字符串
c[0] = 'h'        // cannot assign to c[0]  編譯時報錯
a := "Kylin Lab" //string字面量
a[0] = 'h'       // cannot assign to c[0]  編譯時報錯

若我們利用unsafe讓一個[]byte復(fù)用這個字符串c的底層數(shù)組,就可以繞過Go語法層面的限制,修改底層數(shù)組的內(nèi)容了。

但是盡量不要這樣做,如果不確定這個字符串會在哪里用到的話~

    a := "Kylin Lab"
	c := "Hello" + a
	var s []byte
	strHeader := (*reflect.StringHeader)(unsafe.Pointer(&c))
	sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&s))
	sliceHeader.Data = strHeader.Data
	sliceHeader.Len = strHeader.Len
	sliceHeader.Cap = strHeader.Len
	s[0] = 'h'
	fmt.Println(c)         //hello Kylin Lab
	fmt.Println(a)         //Kylin Lab
	fmt.Println(string(s)) //hello Kylin Lab

到此這篇關(guān)于GoLang unsafe包詳細講解的文章就介紹到這了,更多相關(guān)GoLang unsafe內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang Copier入門到入坑探究

    Golang Copier入門到入坑探究

    這篇文章主要為大家介紹了Golang Copier入門到入坑探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-11-11
  • Golang編程并發(fā)工具庫MapReduce使用實踐

    Golang編程并發(fā)工具庫MapReduce使用實踐

    這篇文章主要為大家介紹了Golang并發(fā)工具庫MapReduce的使用實踐,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-04-04
  • 解決golang sync.Wait()不執(zhí)行的問題

    解決golang sync.Wait()不執(zhí)行的問題

    這篇文章主要介紹了解決golang sync.Wait()不執(zhí)行的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • golang壓縮與解壓縮文件的示例代碼

    golang壓縮與解壓縮文件的示例代碼

    這篇文章主要給大家介紹了golang壓縮與解壓縮文件,文中通過代碼示例給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-02-02
  • 一文初探?Goroutine?與?channel基本用法

    一文初探?Goroutine?與?channel基本用法

    這篇文章主要為大家介紹了一文初探?Goroutine?與?channel基本用法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • GO語言判斷一個網(wǎng)段是否屬于另一個網(wǎng)段的子網(wǎng)

    GO語言判斷一個網(wǎng)段是否屬于另一個網(wǎng)段的子網(wǎng)

    這篇文章主要介紹了GO語言判斷一個網(wǎng)段是否屬于另一個網(wǎng)段的子網(wǎng)的相關(guān)資料,內(nèi)容介紹詳細,具有一定的參考價值,需要的朋友可任意參考一下
    2022-03-03
  • 從零封裝Gin框架及項目初始化教程

    從零封裝Gin框架及項目初始化教程

    這篇文章主要為大家介紹了從零封裝Gin框架及項目的初始化教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2024-01-01
  • goland設(shè)置控制臺折疊效果

    goland設(shè)置控制臺折疊效果

    這篇文章主要介紹了goland設(shè)置控制臺折疊效果,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-12-12
  • go-zero 應(yīng)對海量定時/延遲任務(wù)的技巧

    go-zero 應(yīng)對海量定時/延遲任務(wù)的技巧

    這篇文章主要介紹了go-zero 如何應(yīng)對海量定時/延遲任務(wù),本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-10-10
  • golang中defer的使用規(guī)則詳解

    golang中defer的使用規(guī)則詳解

    大家應(yīng)該都知道在golang當(dāng)中,defer代碼塊會在函數(shù)調(diào)用鏈表中增加一個函數(shù)調(diào)用。下面這篇文章主要給大家介紹了關(guān)于golang中defer的使用規(guī)則,文中介紹的非常詳細,對大家具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。
    2017-07-07

最新評論