亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

GoLang unsafe包詳細(xì)講解

 更新時(shí)間:2022年10月12日 08:34:45   作者:~龐貝  
從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

1.前言

開發(fā)中,[]byte類型和string類型需要互相轉(zhuǎn)換的場景并不少見,直接的想法是像下面這樣進(jìn)行強(qiáng)制類型轉(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進(jìn)行修改,那么這樣轉(zhuǎn)換就沒什么問題,但是如果只是因?yàn)轭愋筒缓线m,并不需要對轉(zhuǎn)換后的變量做任何修改,那這樣轉(zhuǎn)換就顯得不劃算了。我們知道,[]byte和string的內(nèi)存布局如下圖所示:

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

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

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

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

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

現(xiàn)在我們通過unsafe.Pointer把b的指針轉(zhuǎn)換為*string類型,我們可以放心的這樣做,是因?yàn)槲覀冎纒lice的底層布局與string是兼容的,b的前兩項(xiàng)內(nèi)容與a相同,都是一個(gè)uintptr和一個(gè)int。可參見reflect包中關(guān)于這兩個(gè)類型的定義:

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

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

接下來,我們把a(bǔ)賦值給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ù)組,元素個(gè)數(shù)也填好了~

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

這時(shí)候就要拿出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//引用不會出錯(cuò)
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//引用會出錯(cuò)

3.指針運(yùn)算

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

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

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

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

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

即便如此,我們也不能直接通過uintptr來修改b的容量,因?yàn)樗皇侵羔橆愋停乙膊荒苤苯愚D(zhuǎn)換為指針類型。但是可以通過unsafe.Pointer類型中轉(zhuǎn)一下。

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

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

*tmp2 = len(b)

目前為止,我們已經(jīng)借助unsafe的兩個(gè)法寶,成功完成了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類型的臨時(shí)變量來存儲一個(gè)地址,然后才把它轉(zhuǎn)換為某個(gè)指針類型。

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

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

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

4.獲取大小和偏移

unsafe.Sizeof可以拿到任意類型的大小,unsafe.Alignof可以拿到任意類型的對齊邊界。按照reflect.SliceHeader的定義,我們這里可以用unsafe.Sizeof來獲取uintptr和int的大小,b的起始地址偏移這么多就是第三個(gè)字段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

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

這里這樣寫可行,是因?yàn)槲覀冎纔intptr和int的大小不是4字節(jié)就是8字節(jié),無論哪一種,都會緊挨著第三個(gè)字段,不會出現(xiàn)因內(nèi)存對齊而形成的間隙。

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

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

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

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

unsafe.Offsetof(bPtr.Cap)

再然后,就是把這個(gè)字段的地址轉(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的功能,刻意繞了個(gè)遠(yuǎn)~

其實(shí)都把b轉(zhuǎn)換為reflect.SliceHeader結(jié)構(gòu)體了,改個(gè)字段值哪里要這么麻煩?。。∥覀兇罂梢赃@樣做:

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

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

    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,我們還要啰嗦一點(diǎn),Go語言中string變量的內(nèi)容默認(rèn)是不會被修改的,而我們通過給string變量整體賦新值的方式來改變它的內(nèi)容時(shí),實(shí)際上會重新分配它的底層數(shù)組。

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

    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'
	/*運(yùn)行報(bào)錯(cuò):
              unexpected fault address 0x6d1875                     
              fatal error: fault                                    
              [signal 0xc0000005 code=0x1 addr=0x6d1875 pc=0x6c013a]*/

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

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

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

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

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

相關(guān)文章

  • Golang Copier入門到入坑探究

    Golang Copier入門到入坑探究

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

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

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

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

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

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

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

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

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

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

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

    從零封裝Gin框架及項(xiàng)目初始化教程

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

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

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

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

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

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

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

最新評論