Go中非類(lèi)型安全unsafe包的詳細(xì)使用
一、Go中的 unsafe 概述
1.1 什么是unsafe?
Go 語(yǔ)言中的 unsafe 包是一個(gè)既強(qiáng)大又危險(xiǎn)的工具,它允許我們繞過(guò) Go 的類(lèi)型系統(tǒng),直接操作內(nèi)存。雖然它在某些高性能場(chǎng)景下非常有用,但使用不當(dāng)也會(huì)導(dǎo)致程序崩潰或安全漏洞。
unsafe 是 Go 語(yǔ)言中的一個(gè)特殊包,它提供了一些可以繞過(guò) Go 類(lèi)型安全機(jī)制的機(jī)制。通過(guò) unsafe,你可以:
- 獲取變量的內(nèi)存地址
- 直接讀寫(xiě)內(nèi)存
- 將任意類(lèi)型轉(zhuǎn)換為
uintptr(指針的整數(shù)表示) - 訪問(wèn)結(jié)構(gòu)體的私有字段
?? 注意:使用 unsafe 會(huì)破壞 Go 的類(lèi)型安全和內(nèi)存安全,應(yīng)謹(jǐn)慎使用,并盡量避免在生產(chǎn)代碼中濫用。
1.2 使用unsafe的注意事項(xiàng)
- 不保證兼容性:
unsafe的實(shí)現(xiàn)可能隨 Go 版本變化,代碼可能在新版本中失效。 - GC 無(wú)法追蹤
uintptr:如果將uintptr轉(zhuǎn)換為unsafe.Pointer后沒(méi)有立即使用,可能會(huì)被 GC 回收,導(dǎo)致非法訪問(wèn)。 - 類(lèi)型安全被破壞:可能導(dǎo)致內(nèi)存損壞、數(shù)據(jù)競(jìng)爭(zhēng)或程序崩潰。
- 可讀性差:濫用
unsafe會(huì)讓代碼難以理解和維護(hù)。
1.3 指針轉(zhuǎn)換規(guī)則
Go 語(yǔ)言中存在三種類(lèi)型的指針,它們分別是:常用的 *T、unsafe.Pointer 及 uintptr??梢钥偨Y(jié)出這三者的轉(zhuǎn)換規(guī)則:
- 任何類(lèi)型的 *T 都可以轉(zhuǎn)換為 unsafe.Pointer;
- unsafe.Pointer 也可以轉(zhuǎn)換為任何類(lèi)型的 *T;unsafe.Pointer 可以轉(zhuǎn)換為 uintptr;
- uintptr 也可以轉(zhuǎn)換為 unsafe.Pointer。

可以發(fā)現(xiàn),unsafe.Pointer 主要用于指針類(lèi)型的轉(zhuǎn)換,而且是各個(gè)指針類(lèi)型轉(zhuǎn)換的橋梁。
二、unsafe的核心內(nèi)容
2.1unsafe.Pointer
unsafe.Pointer 是一種特殊的指針類(lèi)型,它可以指向任意類(lèi)型的數(shù)據(jù)。它和普通指針(如 *int)之間的主要區(qū)別是:
- 普通指針不能隨意轉(zhuǎn)換類(lèi)型
unsafe.Pointer可以和uintptr互相轉(zhuǎn)換,從而實(shí)現(xiàn)指針運(yùn)算
var x int = 42 p := unsafe.Pointer(&x) // &x 是 *int 類(lèi)型,可以轉(zhuǎn)換為 unsafe.Pointer
2.2uintptr
uintptr 是一個(gè)整數(shù)類(lèi)型,足夠大以存儲(chǔ)任意指針的值。它常用于指針運(yùn)算,例如:
p := unsafe.Pointer(&x) ptr := uintptr(p) // 轉(zhuǎn)換為 uintptr ptr += 8 // 指針運(yùn)算 p = unsafe.Pointer(ptr) // 再轉(zhuǎn)回 unsafe.Pointer
?? 注意:uintptr 不是指針,它不會(huì)被 GC 追蹤,因此不能長(zhǎng)時(shí)間持有。
2.3unsafe.Sizeof、unsafe.Alignof、unsafe.Offsetof
1、unsafe.Sizeof
Sizeof:返回類(lèi)型或變量的大小(字節(jié))。Sizeof 函數(shù)可以返回一個(gè)類(lèi)型所占用的內(nèi)存大小,這個(gè)大小只與類(lèi)型有關(guān),和類(lèi)型對(duì)應(yīng)的變量存儲(chǔ)的內(nèi)容大小無(wú)關(guān),比如 bool 型占用一個(gè)字節(jié)、int8 也占用一個(gè)字節(jié)。
通過(guò) Sizeof 函數(shù)你可以查看任何類(lèi)型(比如字符串、切片、整型)占用的內(nèi)存大小,示例代碼如下:
fmt.Println(unsafe.Sizeof(true))
fmt.Println(unsafe.Sizeof(int8(0)))
fmt.Println(unsafe.Sizeof(int16(10)))
fmt.Println(unsafe.Sizeof(int32(10000000)))
fmt.Println(unsafe.Sizeof(int64(10000000000000)))
fmt.Println(unsafe.Sizeof(int(10000000000000000)))
fmt.Println(unsafe.Sizeof(string("數(shù)據(jù)知道")))
fmt.Println(unsafe.Sizeof([]string{"數(shù)據(jù)u知道","張三"}))
對(duì)于整型來(lái)說(shuō),占用的字節(jié)數(shù)意味著這個(gè)類(lèi)型存儲(chǔ)數(shù)字范圍的大小,比如 int8 占用一個(gè)字節(jié),也就是 8bit,所以它可以存儲(chǔ)的大小范圍是 -128~~127,也就是 −2^(n-1) 到 2^(n-1)−1。其中 n 表示 bit,int8 表示 8bit,int16 表示 16bit,以此類(lèi)推。
小提示:一個(gè) struct 結(jié)構(gòu)體的內(nèi)存占用大小,等于它包含的字段類(lèi)型內(nèi)存占用大小之和。
2、Alignof
Alignof:是 Go 語(yǔ)言 unsafe 包中的一個(gè)函數(shù),用于返回某個(gè)類(lèi)型的對(duì)齊系數(shù)(alignment),即該類(lèi)型的變量在內(nèi)存中存放時(shí)的起始地址必須是其對(duì)齊系數(shù)的整數(shù)倍。
func Alignof(x ArbitraryType) uintptr
- 參數(shù):x 可以是任意類(lèi)型的表達(dá)式(通常傳遞一個(gè)變量或零值)。
- 返回值:uintptr,表示該類(lèi)型的對(duì)齊系數(shù)(單位是字節(jié))。
3、Offsetof
Offsetof:返回結(jié)構(gòu)體字段相對(duì)于結(jié)構(gòu)體起始地址的偏移量
三、案例分析
3.1 案例 1:使用unsafe修改結(jié)構(gòu)體私有字段
3.2 案例 2:指針運(yùn)算模擬數(shù)組訪問(wèn)
package main
import (
"fmt"
"unsafe"
)
func main() {
arr := [3]int{10, 20, 30}
// 獲取數(shù)組首地址
basePtr := unsafe.Pointer(&arr[0])
// 模擬指針運(yùn)算訪問(wèn)第二個(gè)元素
secondPtr := (*int)(unsafe.Pointer(uintptr(basePtr) + unsafe.Sizeof(arr[0])))
fmt.Println(*secondPtr) // 輸出 20
}
3.3 案例 3:string與[]byte的零拷貝轉(zhuǎn)換
package main
import (
"fmt"
"unsafe"
)
func main() {
s := "hello, unsafe"
// 獲取 string 的底層結(jié)構(gòu)
strHeader := (*struct {
data uintptr
len int
})(unsafe.Pointer(&s))
// 構(gòu)造 []byte 的底層結(jié)構(gòu)
bytes := *(*[]byte)(unsafe.Pointer(&struct {
data uintptr
len int
cap int
}{
data: strHeader.data,
len: strHeader.len,
cap: strHeader.len,
}))
fmt.Println(string(bytes)) // 輸出 "hello, unsafe"
}
總結(jié):unsafe 是 Go 語(yǔ)言中的一把“雙刃劍”。unsafe 包里的功能雖然不安全,但的確很香,比如指針運(yùn)算、類(lèi)型轉(zhuǎn)換等,都可以幫助我們提高性能。不過(guò)還是建議盡可能地不使用,因?yàn)樗梢岳@開(kāi) Go 語(yǔ)言編譯器的檢查,可能會(huì)因?yàn)槟愕牟僮魇д`而出現(xiàn)問(wèn)題。當(dāng)然如果是需要提高性能的必要操作,還是可以使用,比如 []byte 轉(zhuǎn) string,就可以通過(guò) unsafe.Pointer 實(shí)現(xiàn)零內(nèi)存拷貝,
到此這篇關(guān)于Go中非類(lèi)型安全unsafe包的詳細(xì)使用的文章就介紹到這了,更多相關(guān)Go 非類(lèi)型安全unsafe包內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
GoFrame?gredis配置文件及配置方法對(duì)比
這篇文章主要為大家介紹了GoFrame?gredis配置管理中,配置文件及配置方法對(duì)比,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Go實(shí)現(xiàn)用戶(hù)每日限額的方法(例一天只能領(lǐng)三次福利)
這篇文章主要介紹了Go實(shí)現(xiàn)用戶(hù)每日限額的方法(例一天只能領(lǐng)三次福利)2022-01-01
go語(yǔ)言通過(guò)反射獲取和設(shè)置結(jié)構(gòu)體字段值的方法
這篇文章主要介紹了go語(yǔ)言通過(guò)反射獲取和設(shè)置結(jié)構(gòu)體字段值的方法,實(shí)例分析了Go語(yǔ)言反射的使用技巧,需要的朋友可以參考下2015-03-03
Golang小數(shù)操作指南之判斷小數(shù)點(diǎn)位數(shù)與四舍五入
這篇文章主要給大家介紹了關(guān)于Golang小數(shù)操作指南之判斷小數(shù)點(diǎn)位數(shù)與四舍五入的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-03-03
通過(guò)案例簡(jiǎn)單聊聊為什么說(shuō)Go中的字符串是不能被修改的
在接觸Go這么語(yǔ)言,可能你經(jīng)常會(huì)聽(tīng)到這樣一句話,對(duì)于字符串不能修改,可能你很納悶,日常開(kāi)發(fā)中我們對(duì)字符串進(jìn)行修改也是很正常的,為什么又說(shuō)Go中的字符串不能進(jìn)行修改呢,本文就來(lái)通過(guò)實(shí)際案例給大家演示,為什么Go中的字符串不能進(jìn)行修改2023-07-07
GoFrame基于性能測(cè)試得知grpool使用場(chǎng)景
這篇文章主要為大家介紹了GoFrame基于性能測(cè)試得知grpool使用場(chǎng)景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06

