溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

golang中unsafe 和 uintptr 指針怎么用

發(fā)布時(shí)間:2021-12-15 09:38:08 來源:億速云 閱讀:174 作者:小新 欄目:云計(jì)算

這篇文章將為大家詳細(xì)講解有關(guān)golang中unsafe 和 uintptr 指針怎么用,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

1.golang中的指針類型

三個(gè)類型
其實(shí)指針有三種:
一種是我們常見的*,用*去表示的指針;
一種是unsafe.Pointer,Pointer是unsafe包下的一個(gè)類型;
最后一種是uintptr,uintptr 這玩意是可以進(jìn)行運(yùn)算的也就是可以++–;

他們之間有這樣的轉(zhuǎn)換關(guān)系:
*<=> unsafe.Pointer <=> uintptr

有一點(diǎn)要注意的是,uintptr 并沒有指針的語義,意思就是 uintptr 所指向的對(duì)象會(huì)被 gc 無情地回收。而 unsafe.Pointer 有指針語義,可以保護(hù)它所指向的對(duì)象在“有用”的時(shí)候不會(huì)被垃圾回收。
從這樣的關(guān)系你大概就可以猜到,我們使用的指針*p轉(zhuǎn)換成Pointer然后轉(zhuǎn)換uintptr進(jìn)行運(yùn)算之后再原路返回,理論上就能等同于進(jìn)行了指針的運(yùn)算。我們下面就來實(shí)踐一下。

2.具體操作

unsafe操作slice

func main() {s := make([]int, 10)s[1] = 2
    p := &s[0]fmt.Println(*p)
    up := uintptr(unsafe.Pointer(p)) //這里有可能會(huì)被回收 所以最好寫成 (*int)unsafe.Pointer(uintptr(unsafe.Pointer(p))+unsafe.Sizeof(int(0)))up += unsafe.Sizeof(int(0)) // 這里不是up++p2 := (*int)(unsafe.Pointer(up))fmt.Println(*p2)}

輸出:
0
2

從代碼中我們可以看到,我們首先將指針指向切片的第一個(gè)位置,然后通過轉(zhuǎn)換得到uintptr,操作uintptr + 上8位(注意這里不能++因?yàn)榇娣诺氖莍nt,下一個(gè)元素位置相隔舉例int個(gè)字節(jié)),最后轉(zhuǎn)換回來得到指針,取值,就能取到切片的第二個(gè)位置了。

unsafe操作struct(可以訪問私有屬性)
我們知道如果一個(gè)結(jié)構(gòu)體里面定義的屬性是私有的,那么這個(gè)屬性是不能被外界訪問到的。我們來看看下面這個(gè)操作:

package maintype User struct {age  intname string}package mainfunc main() {user := &User{}fmt.Println(user)
    s := (*int)(unsafe.Pointer(user))*s = 15up := uintptr(unsafe.Pointer(user)) + unsafe.Sizeof(int(0))namep := (*string)(unsafe.Pointer(up))*namep = "ljy" 

    fmt.Println(user)}

User是另外一個(gè)basic包中的結(jié)構(gòu)體,其中的age是小寫開頭的,理論上來說,我們?cè)谕獠繘]有辦法修改age的值,但是經(jīng)過上面這波操作之后,輸出信息是:
&{0 }
&{10 xxx}
也就是說成功操作到了結(jié)構(gòu)體的私有屬性。

順便提一句:創(chuàng)建結(jié)構(gòu)體會(huì)被分配一塊連續(xù)的內(nèi)存,結(jié)構(gòu)體的地址也代表了第一個(gè)成員的地址。

字符串和byte數(shù)組轉(zhuǎn)換inplace
我們知道如果將字符串轉(zhuǎn)換成[]byte非常方便

s := "123"a := []byte(s)

但是這樣需要開辟額外的空間,那么如何實(shí)現(xiàn)原地的,不需要拷貝數(shù)據(jù)的轉(zhuǎn)換呢?
其實(shí)從底層的存儲(chǔ)角度來說,string的存儲(chǔ)規(guī)則和[]byte是一樣的,也就是說,其實(shí)指針都是從某個(gè)位置開始到一段空間,中間一格一格。所以利用unsafe就可以做到。

func main() {s := "123"a := []byte(s)print("s = " , &s, "\n")print("a = " , &a, "\n")
    a2 := (*[]byte)(unsafe.Pointer(&s))print("a2 = " , a2, "\n")fmt.Println(*a2)}輸出結(jié)果:
s = 0xc420055f40a = 0xc420055f60a2 = 0xc420055f40[49 50 51]

我們可以看到s和a的地址是不一樣的,但是s和a2的地址是一樣的,并且a2已經(jīng)是一個(gè)[]byte了。

存在的問題
其實(shí)這個(gè)轉(zhuǎn)換是存在問題的,問題就在新的[]byte的Cap沒有正確的初始化。
我們打印一下cap看一下

fmt.Println(“cap a =”, cap(a))  fmt.Println(“cap a2 =”, cap(*a2))  結(jié)果是:cap a = 32cap a2 = 17418400

問題的原因
在src/reflect/value.go下看

type StringHeader struct {
	Data uintptr
	Len  int}type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int}

看到其實(shí)string沒有cap而[]byte有,所以導(dǎo)致問題出現(xiàn),也容易理解,string是沒有容量擴(kuò)容這個(gè)說法的,所以新的[]byte沒有賦值cap所以使用了默認(rèn)值。

問題解決

stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))bh := reflect.SliceHeader{
	Data: stringHeader.Data,
	Len:  stringHeader.Len,
	Cap:  stringHeader.Len,}return *(*[]byte)(unsafe.Pointer(&bh))

通過重新設(shè)置SliceHeader就可以完成

關(guān)于“golang中unsafe 和 uintptr 指針怎么用”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI