Golang?中的?unsafe.Pointer?和?uintptr詳解
前言
日常開發(fā)中經(jīng)??吹酱罄袀冇酶鞣N unsafe.Pointer, uintptr 搞各種花活,作為小白一看到 unsafe 就發(fā)憷,不了解二者的區(qū)別和場景,自然心里沒數(shù)。今天我們就來學(xué)習(xí)下這部分知識。
uintptr
uintptr 的定義在 builtin 包下,定義如下:
// uintptr is an integer type that is large enough to hold the bit pattern of // any pointer. type uintptr uintptr
參照注釋我們知道:
- uintptr 是一個整數(shù)類型(這個非常重要),注意,他不是個指針;
- 但足夠保存任何一種指針類型。
unsafe 包支持了這些方法來完成【類型】=> uintptr 的轉(zhuǎn)換:
func Sizeof(x ArbitraryType) uintptr func Offsetof(x ArbitraryType) uintptr func Alignof(x ArbitraryType) uintptr
你可以將任意類型變量轉(zhuǎn)入,獲取對應(yīng)語義的 uintptr,用來后續(xù)計算內(nèi)存地址(比如基于一個結(jié)構(gòu)體字段地址,獲取下一個字段地址等)。
unsafe.Pointer
我們來看一下什么是 unsafe 包下的 Pointer:
// ArbitraryType is here for the purposes of documentation only and is not actually // part of the unsafe package. It represents the type of an arbitrary Go expression. type ArbitraryType int // Pointer represents a pointer to an arbitrary type. There are four special operations // available for type Pointer that are not available for other types: // - A pointer value of any type can be converted to a Pointer. // - A Pointer can be converted to a pointer value of any type. // - A uintptr can be converted to a Pointer. // - A Pointer can be converted to a uintptr. // Pointer therefore allows a program to defeat the type system and read and write // arbitrary memory. It should be used with extreme care. type Pointer *ArbitraryType
這里的 ArbitraryType 僅僅是為了便于開發(fā)者理解。語義上來講你可以把 Pointer 理解為一個可以指向任何一種類型的【指針】。
這一點很關(guān)鍵。我們此前遇到的場景一般都是,先定義一個類型,然后就有了這個類型對應(yīng)的指針。而 unsafe.Pointer 則是一個通用的解法,不管你是什么類型都可以。突破了這層限制,我們就可以在運(yùn)行時具備更多能力,也方便適配一些通用場景。
官方提供了四種 Pointer 支持的場景:
- 任意類型的指針可以轉(zhuǎn)換為一個 Pointer;
- 一個 Pointer 也可以被轉(zhuǎn)為任意類型的指針;
- uintptr 可以被轉(zhuǎn)換為 Pointer;
- Pointer 也可以被轉(zhuǎn)換為 uintptr。
這樣強(qiáng)大的能力使我們能夠繞開【類型系統(tǒng)】,丟失了編譯期的校驗,所以使用時一定要小心。
使用姿勢
常規(guī)類型互轉(zhuǎn)
func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }
我們?nèi)?f 的指針,將其轉(zhuǎn)為 unsafe.Pointer,再轉(zhuǎn)為一個 uint64 的指針,最后解出來值。
其實本質(zhì)就是把 unsafe.Pointer 當(dāng)成了一個媒介。用到了他可以從任意一個類型轉(zhuǎn)換得來,也可以轉(zhuǎn)為任意一個類型。
這樣的用法有一定的前提:
- 轉(zhuǎn)化的目標(biāo)類型(uint64) 的 size 一定不能比原類型 (float64)還大(二者size都是8個字節(jié));
- 前后兩種類型有等價的 memory layout;
比如,int8 轉(zhuǎn)為 int64 是不支持的,我們測試一下:
package main import ( "fmt" "unsafe" ) func main() { fmt.Println("int8 => int64", Int8To64(5)) fmt.Println("int64 => int8", Int64To8(5)) } func Int64To8(f int64) int8 { return *(*int8)(unsafe.Pointer(&f)) } func Int8To64(f int8) int64 { return *(*int64)(unsafe.Pointer(&f)) }
運(yùn)行后你會發(fā)現(xiàn),int64 => int8 轉(zhuǎn)換正常,從小到大則會出問題:
int8 => int64 1079252997 int64 => int8 5 Program exited.
Pointer => uintptr
從 Pointer 轉(zhuǎn) uintptr 本質(zhì)產(chǎn)出的是這個 Pointer 指向的值的內(nèi)存地址,一個整型。
這里還是要在強(qiáng)調(diào)一下:
- uintptr 指的是具體的內(nèi)存地址,不是個指針,沒有指針的語義,你可以將 uintptr 打印出來比對地址是否相同。
- 即便某個對象因為 GC 等原因被回收,uintptr的值也不會連帶著變動。
- uintptr地址關(guān)聯(lián)的對象可以被垃圾回收。GC不認(rèn)為uintptr是活引用,因此unitptr地址指向的對象可以被垃圾收集。
指針?biāo)銛?shù)計算:Pointer => uintptr => Pointer
將一個指針轉(zhuǎn)為 uintptr 將會得到它指向的內(nèi)存地址,而我們又可以結(jié)合 SizeOf,AlignOf,Offsetof 來計算出來另一個 uintptr 進(jìn)行計算。
這類場景最常見的是【獲取結(jié)構(gòu)體中的變量】或【數(shù)組中的元素】。
比如:
f := unsafe.Pointer(&s.f) f := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f)) e := unsafe.Pointer(&x[i]) e := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + i*unsafe.Sizeof(x[0]))
上面這兩組運(yùn)算本質(zhì)是相同的,一種是直接拿地址,一種是通過計算 size,offset 來實現(xiàn)。
注意:變量到 uintptr 的轉(zhuǎn)換以及計算必須在一個表達(dá)式中完成(需要保證原子性):
錯誤的案例:
u := uintptr(p) p = unsafe.Pointer(u + offset)
uintptr 到 Pointer 的轉(zhuǎn)換一定要在一個表達(dá)式,不能用 uintptr 存起來,下個表達(dá)式再轉(zhuǎn)。
uintptr + offset 算地址,再跟 Pointer 轉(zhuǎn)化其實是一個很強(qiáng)大的能力,我們再來看一個實際的例子:
package main import ( "fmt" "unsafe" ) func main() { length := 6 arr := make([]int, length) for i := 0; i < length; i++ { arr[i] = i } fmt.Println(arr) // [0 1 2 3 4 5] // 取slice的第5個元素:通過計算第1個元素 + 4 個元素的size 得出 end := unsafe.Pointer(uintptr(unsafe.Pointer(&arr[0])) + 4*unsafe.Sizeof(arr[0])) fmt.Println(*(*int)(end)) // 4 fmt.Println(arr[4]) // 4 }
unsafe.Pointer 不能進(jìn)行算數(shù)計算,uintptr 其實是很好的一個補(bǔ)充。
reflect 包中從 uintptr => Ptr
我們知道,reflect 的 Value 提供了兩個方法 Pointer 和 UnsafeAddr 返回 uintptr。這里不使用 unsafe.Pointer 的用意在于避免用戶不 import unsafe 包就能將結(jié)果轉(zhuǎn)成任意類型,但這也帶來了問題。
上面有提到,千萬不能先保存一個 uintptr,再轉(zhuǎn) unsafe.Pointer,這樣的結(jié)果是很不可靠的。所以我們必須在調(diào)用完 Pointer/UnsafeAddr 之后就立刻轉(zhuǎn) unsafe.Pointer。
正例:
p := (*int)(unsafe.Pointer(reflect.ValueOf(new(int)).Pointer()))
反例:
u := reflect.ValueOf(new(int)).Pointer() p := (*int)(unsafe.Pointer(u))
實戰(zhàn)案例
string vs []byte
活學(xué)活用,其實參照上面轉(zhuǎn)換的第一個案例就可以實現(xiàn),不需要 uintptr。還是一樣的思路,用 unsafe.Pointer 作為媒介,指針轉(zhuǎn)換結(jié)束后,解指針拿到值即可。
import ( "unsafe" ) func BytesToString(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } func StringToBytes(s string) []byte { return *(*[]byte)(unsafe.Pointer(&s)) }
其實這里從 []byte 轉(zhuǎn) string 的操作就是和 strings 包下 Builder 的設(shè)計一致的:
// A Builder is used to efficiently build a string using Write methods. // It minimizes memory copying. The zero value is ready to use. // Do not copy a non-zero Builder. type Builder struct { addr *Builder // of receiver, to detect copies by value buf []byte } // String returns the accumulated string. func (b *Builder) String() string { return *(*string)(unsafe.Pointer(&b.buf)) } // Reset resets the Builder to be empty. func (b *Builder) Reset() { b.addr = nil b.buf = nil } // Write appends the contents of p to b's buffer. // Write always returns len(p), nil. func (b *Builder) Write(p []byte) (int, error) { b.copyCheck() b.buf = append(b.buf, p...) return len(p), nil } // WriteString appends the contents of s to b's buffer. // It returns the length of s and a nil error. func (b *Builder) WriteString(s string) (int, error) { b.copyCheck() b.buf = append(b.buf, s...) return len(s), nil }
strings.Builder 設(shè)計之處就是為了最大程度降低內(nèi)存拷貝。本質(zhì)是維護(hù)了一個 buf 的字節(jié)數(shù)組。
sync.Pool
sync.Pool 的設(shè)計中在本地 pool 沒有可以返回 Get 的元素時,會到其他 poolLocal 偷一個元素回來,這個跳轉(zhuǎn)到其他 pool 的操作就是用 unsafe.Pointer + uintptr + SizeOf 實現(xiàn)的,參考一下:
func indexLocal(l unsafe.Pointer, i int) *poolLocal { lp := unsafe.Pointer(uintptr(l) + uintptr(i)*unsafe.Sizeof(poolLocal{})) return (*poolLocal)(lp) }
到此這篇關(guān)于Golang 中的 unsafe.Pointer 和 uintptr詳解的文章就介紹到這了,更多相關(guān)Golang uintptr內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言服務(wù)器開發(fā)實現(xiàn)最簡單HTTP的GET與POST接口
這篇文章主要介紹了Go語言服務(wù)器開發(fā)實現(xiàn)最簡單HTTP的GET與POST接口,實例分析了Go語言http包的使用技巧,需要的朋友可以參考下2015-02-02golang框架gin的日志處理和zap lumberjack日志使用方式
這篇文章主要介紹了golang框架gin的日志處理和zap lumberjack日志使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01golang結(jié)構(gòu)化日志log/slog包之LogValuer的用法簡介
這篇文章主要為大家詳細(xì)介紹了golang結(jié)構(gòu)化日志log/slog包中 LogValuer 和日志記錄函數(shù)的正確包裝方法,感興趣的小伙伴可以跟隨小編一起了解一下2023-10-10Go語言集成開發(fā)環(huán)境IDE詳細(xì)安裝教程
VSCode是免費(fèi)開源的現(xiàn)代化輕量級代碼編輯器,支持幾乎所有主流的開發(fā)語言,內(nèi)置命令行工具和 Git 版本控制系統(tǒng),支持插件擴(kuò)展,這篇文章主要介紹了Go語言集成開發(fā)環(huán)境IDE詳細(xì)安裝教程,需要的朋友可以參考下2021-11-11golang進(jìn)行簡單權(quán)限認(rèn)證的實現(xiàn)
本文主要介紹了golang簡單權(quán)限認(rèn)證的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09Hugo?Config模塊構(gòu)建實現(xiàn)源碼剖析
這篇文章主要為大家介紹了Hugo?Config模塊構(gòu)建實現(xiàn)源碼剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02Go+Kafka實現(xiàn)延遲消息的實現(xiàn)示例
本文主要介紹了Go+Kafka實現(xiàn)延遲消息的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07