共計 2470 個字符,預計需要花費 7 分鐘才能閱讀完成。
這篇文章將為大家詳細講解有關 golang 中 unsafe 和 uintptr 指針怎么用,丸趣 TV 小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
1.golang 中的指針類型
三個類型
其實指針有三種:
一種是我們常見的 *,用 * 去表示的指針;
一種是 unsafe.Pointer,Pointer 是 unsafe 包下的一個類型;
最后一種是 uintptr,uintptr 這玩意是可以進行運算的也就是可以 ++–;
他們之間有這樣的轉換關系:
* = unsafe.Pointer = uintptr
有一點要注意的是,uintptr 并沒有指針的語義,意思就是 uintptr 所指向的對象會被 gc 無情地回收。而 unsafe.Pointer 有指針語義,可以保護它所指向的對象在“有用”的時候不會被垃圾回收。
從這樣的關系你大概就可以猜到,我們使用的指針 * p 轉換成 Pointer 然后轉換 uintptr 進行運算之后再原路返回,理論上就能等同于進行了指針的運算。我們下面就來實踐一下。
2. 具體操作
unsafe 操作 slice
func main() {s := make([]int, 10)s[1] = 2
p := s[0]fmt.Println(*p)
up := uintptr(unsafe.Pointer(p)) // 這里有可能會被回收 所以最好寫成 (*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
從代碼中我們可以看到,我們首先將指針指向切片的第一個位置,然后通過轉換得到 uintptr,操作 uintptr + 上 8 位(注意這里不能 ++ 因為存放的是 int,下一個元素位置相隔舉例 int 個字節),最后轉換回來得到指針,取值,就能取到切片的第二個位置了。
unsafe 操作 struct(可以訪問私有屬性)
我們知道如果一個結構體里面定義的屬性是私有的,那么這個屬性是不能被外界訪問到的。我們來看看下面這個操作:
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 是另外一個 basic 包中的結構體,其中的 age 是小寫開頭的,理論上來說,我們在外部沒有辦法修改 age 的值,但是經過上面這波操作之后,輸出信息是:
{0}
{10 xxx}
也就是說成功操作到了結構體的私有屬性。
順便提一句:創建結構體會被分配一塊連續的內存,結構體的地址也代表了第一個成員的地址。
字符串和 byte 數組轉換 inplace
我們知道如果將字符串轉換成 []byte 非常方便
s := 123 a := []byte(s)
但是這樣需要開辟額外的空間,那么如何實現原地的,不需要拷貝數據的轉換呢?
其實從底層的存儲角度來說,string 的存儲規則和 []byte 是一樣的,也就是說,其實指針都是從某個位置開始到一段空間,中間一格一格。所以利用 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)} 輸出結果:s = 0xc420055f40a = 0xc420055f60a2 = 0xc420055f40[49 50 51]
我們可以看到 s 和 a 的地址是不一樣的,但是 s 和 a2 的地址是一樣的,并且 a2 已經是一個 []byte 了。
存在的問題
其實這個轉換是存在問題的,問題就在新的 []byte 的 Cap 沒有正確的初始化。
我們打印一下 cap 看一下
fmt.Println(“cap a =”, cap(a)) fmt.Println(“cap a2 =”, cap(*a2)) 結果是: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}
看到其實 string 沒有 cap 而 []byte 有,所以導致問題出現,也容易理解,string 是沒有容量擴容這個說法的,所以新的 []byte 沒有賦值 cap 所以使用了默認值。
問題解決
stringHeader := (*reflect.StringHeader)(unsafe.Pointer( s))bh := reflect.SliceHeader{
Data: stringHeader.Data,
Len: stringHeader.Len,
Cap: stringHeader.Len,}return *(*[]byte)(unsafe.Pointer( bh))
通過重新設置 SliceHeader 就可以完成
關于“golang 中 unsafe 和 uintptr 指針怎么用”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。