您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)golang中unsafe 和 uintptr 指針怎么用,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
三個(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í)踐一下。
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)把它分享出去讓更多的人看到。
免責(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)容。