欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang?中的?unsafe.Pointer?和?uintptr詳解

 更新時(shí)間:2022年08月05日 14:37:18   作者:ag9920  
這篇文章主要介紹了Golang中的unsafe.Pointer和uintptr詳解,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下

前言

日常開(kāi)發(fā)中經(jīng)??吹酱罄袀冇酶鞣N unsafe.Pointer, uintptr 搞各種花活,作為小白一看到 unsafe 就發(fā)憷,不了解二者的區(qū)別和場(chǎng)景,自然心里沒(méi)數(shù)。今天我們就來(lái)學(xué)習(xí)下這部分知識(shí)。

uintptr

uintptr 的定義在 builtin 包下,定義如下:

// uintptr is an integer type that is large enough to hold the bit pattern of
// any pointer.
type uintptr uintptr

參照注釋我們知道:

  • uintptr 是一個(gè)整數(shù)類(lèi)型(這個(gè)非常重要),注意,他不是個(gè)指針;
  • 但足夠保存任何一種指針類(lèi)型。

unsafe 包支持了這些方法來(lái)完成【類(lèi)型】=> uintptr 的轉(zhuǎn)換:

func Sizeof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr

你可以將任意類(lèi)型變量轉(zhuǎn)入,獲取對(duì)應(yīng)語(yǔ)義的 uintptr,用來(lái)后續(xù)計(jì)算內(nèi)存地址(比如基于一個(gè)結(jié)構(gòu)體字段地址,獲取下一個(gè)字段地址等)。

unsafe.Pointer

我們來(lái)看一下什么是 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 僅僅是為了便于開(kāi)發(fā)者理解。語(yǔ)義上來(lái)講你可以把 Pointer 理解為一個(gè)可以指向任何一種類(lèi)型的【指針】。

這一點(diǎn)很關(guān)鍵。我們此前遇到的場(chǎng)景一般都是,先定義一個(gè)類(lèi)型,然后就有了這個(gè)類(lèi)型對(duì)應(yīng)的指針。而 unsafe.Pointer 則是一個(gè)通用的解法,不管你是什么類(lèi)型都可以。突破了這層限制,我們就可以在運(yùn)行時(shí)具備更多能力,也方便適配一些通用場(chǎng)景。

官方提供了四種 Pointer 支持的場(chǎng)景:

  • 任意類(lèi)型的指針可以轉(zhuǎn)換為一個(gè) Pointer;
  • 一個(gè) Pointer 也可以被轉(zhuǎn)為任意類(lèi)型的指針;
  • uintptr 可以被轉(zhuǎn)換為 Pointer;
  • Pointer 也可以被轉(zhuǎn)換為 uintptr。

這樣強(qiáng)大的能力使我們能夠繞開(kāi)【類(lèi)型系統(tǒng)】,丟失了編譯期的校驗(yàn),所以使用時(shí)一定要小心。

使用姿勢(shì)

常規(guī)類(lèi)型互轉(zhuǎn)

func Float64bits(f float64) uint64 {
    return *(*uint64)(unsafe.Pointer(&f))
}

我們?nèi)?f 的指針,將其轉(zhuǎn)為 unsafe.Pointer,再轉(zhuǎn)為一個(gè) uint64 的指針,最后解出來(lái)值。

其實(shí)本質(zhì)就是把 unsafe.Pointer 當(dāng)成了一個(gè)媒介。用到了他可以從任意一個(gè)類(lèi)型轉(zhuǎn)換得來(lái),也可以轉(zhuǎn)為任意一個(gè)類(lèi)型。

這樣的用法有一定的前提:

  • 轉(zhuǎn)化的目標(biāo)類(lèi)型(uint64) 的 size 一定不能比原類(lèi)型 (float64)還大(二者size都是8個(gè)字節(jié));
  • 前后兩種類(lèi)型有等價(jià)的 memory layout;

比如,int8 轉(zhuǎn)為 int64 是不支持的,我們測(cè)試一下:

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)行后你會(huì)發(fā)現(xiàn),int64 => int8 轉(zhuǎn)換正常,從小到大則會(huì)出問(wèn)題:

int8 => int64 1079252997
int64 => int8 5

Program exited.

Pointer => uintptr

從 Pointer 轉(zhuǎn) uintptr 本質(zhì)產(chǎn)出的是這個(gè) Pointer 指向的值的內(nèi)存地址,一個(gè)整型。

這里還是要在強(qiáng)調(diào)一下:

  • uintptr 指的是具體的內(nèi)存地址,不是個(gè)指針,沒(méi)有指針的語(yǔ)義,你可以將 uintptr 打印出來(lái)比對(duì)地址是否相同。
  • 即便某個(gè)對(duì)象因?yàn)?GC 等原因被回收,uintptr的值也不會(huì)連帶著變動(dòng)。
  • uintptr地址關(guān)聯(lián)的對(duì)象可以被垃圾回收。GC不認(rèn)為uintptr是活引用,因此unitptr地址指向的對(duì)象可以被垃圾收集。

指針?biāo)銛?shù)計(jì)算:Pointer => uintptr => Pointer

將一個(gè)指針轉(zhuǎn)為 uintptr 將會(huì)得到它指向的內(nèi)存地址,而我們又可以結(jié)合 SizeOf,AlignOf,Offsetof 來(lái)計(jì)算出來(lái)另一個(gè) uintptr 進(jìn)行計(jì)算。

這類(lèi)場(chǎng)景最常見(jià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ì)是相同的,一種是直接拿地址,一種是通過(guò)計(jì)算 size,offset 來(lái)實(shí)現(xiàn)。

注意:變量到 uintptr 的轉(zhuǎn)換以及計(jì)算必須在一個(gè)表達(dá)式中完成(需要保證原子性):

錯(cuò)誤的案例:

u := uintptr(p)
p = unsafe.Pointer(u + offset)

uintptr 到 Pointer 的轉(zhuǎn)換一定要在一個(gè)表達(dá)式,不能用 uintptr 存起來(lái),下個(gè)表達(dá)式再轉(zhuǎn)。

uintptr + offset 算地址,再跟 Pointer 轉(zhuǎn)化其實(shí)是一個(gè)很強(qiáng)大的能力,我們?cè)賮?lái)看一個(gè)實(shí)際的例子:

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個(gè)元素:通過(guò)計(jì)算第1個(gè)元素 + 4 個(gè)元素的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ù)計(jì)算,uintptr 其實(shí)是很好的一個(gè)補(bǔ)充。

reflect 包中從 uintptr => Ptr

我們知道,reflect 的 Value 提供了兩個(gè)方法 Pointer 和 UnsafeAddr 返回 uintptr。這里不使用 unsafe.Pointer 的用意在于避免用戶不 import unsafe 包就能將結(jié)果轉(zhuǎn)成任意類(lèi)型,但這也帶來(lái)了問(wèn)題。

上面有提到,千萬(wàn)不能先保存一個(gè) 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))

實(shí)戰(zhàn)案例

string vs []byte

活學(xué)活用,其實(shí)參照上面轉(zhuǎn)換的第一個(gè)案例就可以實(shí)現(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))
}

其實(shí)這里從 []byte 轉(zhuǎn) string 的操作就是和 strings 包下 Builder 的設(shè)計(jì)一致的:

// 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è)計(jì)之處就是為了最大程度降低內(nèi)存拷貝。本質(zhì)是維護(hù)了一個(gè) buf 的字節(jié)數(shù)組。

sync.Pool

sync.Pool 的設(shè)計(jì)中在本地 pool 沒(méi)有可以返回 Get 的元素時(shí),會(huì)到其他 poolLocal 偷一個(gè)元素回來(lái),這個(gè)跳轉(zhuǎn)到其他 pool 的操作就是用 unsafe.Pointer + uintptr + SizeOf 實(shí)現(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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 解析GOROOT、GOPATH、Go-Modules-三者的關(guān)系

    解析GOROOT、GOPATH、Go-Modules-三者的關(guān)系

    這篇文章主要介紹了解析GOROOT、GOPATH、Go-Modules-三者的關(guān)系,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-10-10
  • Go語(yǔ)言使用時(shí)會(huì)遇到的錯(cuò)誤及解決方法詳解

    Go語(yǔ)言使用時(shí)會(huì)遇到的錯(cuò)誤及解決方法詳解

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言使用時(shí)常常會(huì)遇到的一些錯(cuò)誤及解決方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2023-07-07
  • 基于Go語(yǔ)言開(kāi)發(fā)一個(gè)Markdown轉(zhuǎn)HTML工具

    基于Go語(yǔ)言開(kāi)發(fā)一個(gè)Markdown轉(zhuǎn)HTML工具

    這篇文章主要為大家詳細(xì)介紹了如何基于Go語(yǔ)言開(kāi)發(fā)一個(gè)Markdown轉(zhuǎn)HTML工具,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-09-09
  • Gin+Gorm實(shí)現(xiàn)增刪改查的示例代碼

    Gin+Gorm實(shí)現(xiàn)增刪改查的示例代碼

    本文介紹了如何使用Gin和Gorm框架實(shí)現(xiàn)一個(gè)簡(jiǎn)單的增刪改查(CRUD)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-12-12
  • 如何使用Go檢測(cè)用戶本地是否安裝chrome

    如何使用Go檢測(cè)用戶本地是否安裝chrome

    這篇文章主要為大家詳細(xì)介紹了如何使用Go檢測(cè)用戶本地是否安裝chrome,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-10-10
  • GoLang逃逸分析講解

    GoLang逃逸分析講解

    我們都知道go語(yǔ)言中內(nèi)存管理工作都是由Go在底層完成的,這樣我們可以不用過(guò)多的關(guān)注底層的內(nèi)存問(wèn)題。本文主要總結(jié)一下?Golang內(nèi)存逃逸分析,需要的朋友可以參考以下內(nèi)容,希望對(duì)大家有幫助
    2022-12-12
  • 掌握GoLang Fiber路由和中間件技術(shù)進(jìn)行高效Web開(kāi)發(fā)

    掌握GoLang Fiber路由和中間件技術(shù)進(jìn)行高效Web開(kāi)發(fā)

    這篇文章主要為大家介紹了GoLang Fiber路由和中間件進(jìn)行高效Web開(kāi)發(fā),本文將深入探討 Fiber 中的路由細(xì)節(jié),學(xué)習(xí)如何創(chuàng)建和處理路由,深入了解使用路由參數(shù)的動(dòng)態(tài)路由,并掌握在 Fiber 應(yīng)用程序中實(shí)現(xiàn)中間件的藝術(shù)
    2024-01-01
  • Go語(yǔ)言中database/sql的用法介紹

    Go語(yǔ)言中database/sql的用法介紹

    Go語(yǔ)言中的database/sql包定義了對(duì)數(shù)據(jù)庫(kù)的一系列操作,database/sql/driver包定義了應(yīng)被數(shù)據(jù)庫(kù)驅(qū)動(dòng)實(shí)現(xiàn)的接口,這些接口會(huì)被sql包使用,本文將詳細(xì)給大家介紹Go的database/sql的使用方法,需要的朋友可以參考下
    2023-05-05
  • 詳解go語(yǔ)言判斷管道是否關(guān)閉的常見(jiàn)誤區(qū)

    詳解go語(yǔ)言判斷管道是否關(guān)閉的常見(jiàn)誤區(qū)

    這篇文章主要想和大家一起探討一下在Go語(yǔ)言中,我們是否可以使用讀取管道時(shí)的第二個(gè)返回值來(lái)判斷管道是否關(guān)閉,文中的示例代碼講解詳細(xì),有興趣的可以了解下
    2023-10-10
  • Go語(yǔ)言實(shí)現(xiàn)動(dòng)態(tài)開(kāi)點(diǎn)線段樹(shù)詳解

    Go語(yǔ)言實(shí)現(xiàn)動(dòng)態(tài)開(kāi)點(diǎn)線段樹(shù)詳解

    線段樹(shù)是一種用于高效處理區(qū)間查詢和區(qū)間更新的數(shù)據(jù)結(jié)構(gòu),下面我們就來(lái)看看如何使用Go實(shí)現(xiàn)動(dòng)態(tài)開(kāi)點(diǎn)線段樹(shù)的方式,感興趣的可以了解下
    2025-02-02

最新評(píng)論