Go語(yǔ)言普通指針unsafe.Pointer?uintpt之間的關(guān)系及指針運(yùn)算
C 語(yǔ)言指針運(yùn)算
指針運(yùn)算就是對(duì)指針類型的變量做常規(guī)數(shù)學(xué)運(yùn)算,例如加減操作,實(shí)現(xiàn)地址的偏移。指針運(yùn)算在 C 語(yǔ)言中是原生支持的,可以直接在指針變量上做加減,例如:
#include <stdio.h> const int MAX = 3; int main () { int var[] = {10, 100, 200}; int i, *ptr; /* 指針中的數(shù)組地址 */ ptr = var; for ( i = 0; i < MAX; i++) { printf("存儲(chǔ)地址:var[%d] = %p\n", i, ptr ); printf("存儲(chǔ)值:var[%d] = %d\n", i, *ptr ); /* 直接對(duì)指針做++操作,指向下一個(gè)位置 */ ptr++; } return 0; }
結(jié)果
存儲(chǔ)地址:var[0] = e4a298cc
存儲(chǔ)值:var[0] = 10
存儲(chǔ)地址:var[1] = e4a298d0
存儲(chǔ)值:var[1] = 100
存儲(chǔ)地址:var[2] = e4a298d4
存儲(chǔ)值:var[2] = 200
C 語(yǔ)言指針運(yùn)算猶如一把雙刃劍,使用得當(dāng)會(huì)起到事半功倍,有神之一手的效果,反之則會(huì)產(chǎn)生意想不到的 bug 而且很難排查。因?yàn)樵谧鲋羔樳\(yùn)算時(shí)是比較抽象的,具體偏移了多少之后指向到了哪里是非常不直觀的,可能已經(jīng)偏離了設(shè)想中的位置而沒(méi)有發(fā)現(xiàn),運(yùn)行起來(lái)就會(huì)出現(xiàn)錯(cuò)誤。
找出數(shù)組中最小的元素
例如這段 C 代碼,找出數(shù)組中最小的元素:
#include <stdio.h> int findMin(int *arr, int length) { int min = *arr; for (int i = 0; i <= length; i++) { // 注意這里是 i <= length,而不是 i < length printf("i=%d v=%d\n", i, *(arr+i)); if (*(arr + i) < min) { min = *(arr + i); } } return min; } int main() { int arr[] = {1, 2, 3, 4, 5}; int length = sizeof(arr) / sizeof(arr[0]); printf("Min value is: %d\n", findMin(arr, length)); return 0; }
數(shù)組中最小的是 1,可結(jié)果卻是 0:
i=0 v=1
i=1 v=2
i=2 v=3
i=3 v=4
i=4 v=5
i=5 v=0
Min value is: 0
這是由于在 findMin
函數(shù)中循環(huán)條件是 i ≤ length
,超出數(shù)組大小多循環(huán)了一次,實(shí)際上數(shù)組已經(jīng)越界,而 C 語(yǔ)言的數(shù)組實(shí)際上就是指針,C 運(yùn)行時(shí)認(rèn)為這是在指針運(yùn)算,所以不會(huì)報(bào)錯(cuò),導(dǎo)致數(shù)組訪問(wèn)到了其他內(nèi)存地址,最終得到了一個(gè)錯(cuò)誤結(jié)果。
事實(shí)上有很多病毒和外掛的原理就是利用指針來(lái)訪問(wèn)并修改程序運(yùn)行時(shí)內(nèi)存數(shù)據(jù)來(lái)達(dá)到目的。例如游戲外掛可能會(huì)搜索和修改內(nèi)存中的特定值,以改變玩家的生命值、金錢(qián)或其他游戲?qū)傩浴Mㄟ^(guò)指針運(yùn)算,外掛可以直接訪問(wèn)這些內(nèi)存位置并對(duì)其進(jìn)行修改。而病毒可能使用指針運(yùn)算來(lái)插入其自己的代碼到一個(gè)運(yùn)行中的程序,或者篡改程序的正??刂屏?,以達(dá)到其惡意目的。
在 C 語(yǔ)言之后的很多語(yǔ)言多多少少都對(duì)指針做了限制,例如 PHP 中的引用就可以看做是指針的簡(jiǎn)化版,而 Java 甚至干脆移除了指針。
Go 指針運(yùn)算
對(duì)指針做加法會(huì)報(bào)錯(cuò)
在 Go 中默認(rèn)的普通指針也是指代的是一個(gè)內(nèi)存地址,值類似 0x140000ac008
,但 Go 的普通指針不支持指針運(yùn)算的,例如對(duì)指針做加法會(huì)報(bào)錯(cuò):
a := 10 var p *int = &a p = p + 1
報(bào)錯(cuò)
invalid operation: p + 1 (mismatched types *int and untyped int)
但 Go 還是提供了一種直接操作指針的方式,就是 unsafe.Pointer 和 uintptr。
uintptr 是一個(gè)整型,可理解為是將內(nèi)存地址轉(zhuǎn)換成了一個(gè)整數(shù),既然是一個(gè)整數(shù),就可以對(duì)其做數(shù)值計(jì)算,實(shí)現(xiàn)指針地址的加減,也就是地址偏移,類似跟 C 語(yǔ)言中一樣的效果。
而 unsafe.Pointer 是普通指針和 uintptr 之間的橋梁,通過(guò) unsafe.Pointer 實(shí)現(xiàn)三者的相互轉(zhuǎn)換。
*T <-> unsafe.Pointer <-> uintptr
先看看這三位都長(zhǎng)什么樣:
func main() { a := 10 var b *int b = &a fmt.Printf("a is %T, a=%v\n", a, a) fmt.Printf("b is %T, b=%v\n", b, b) p := unsafe.Pointer(b) fmt.Printf("p is %T, p=%v\n", p, p) uptr := uintptr(p) fmt.Printf("uptr is %T, uptr=%v\n", uptr, uptr) }
輸出
a is int, a=10
b is *int, b=0x140000ae008
p is unsafe.Pointer, p=0x140000ae008
uptr is uintptr, uptr=1374390247432
舉一個(gè)通過(guò)指針運(yùn)算修改結(jié)構(gòu)體的例子
type People struct { age int32 height int64 name string } people := &People{} fmt.Println(people) // 將 people 普通指針轉(zhuǎn)成 unsafe.Pointer 再轉(zhuǎn)為 uintptr // 后面再加上 height 字段相對(duì)于結(jié)構(gòu)體本身的偏移量,就得到了 height 的地址的 uintptr 值 // 再將 height 的 uintptr 值轉(zhuǎn)成 unsafe.Pointer 賦值給 height 變量 // 所以現(xiàn)在 height 的類型是 unsafe.Pointer height := unsafe.Pointer(uintptr(unsafe.Pointer(people)) + unsafe.Offsetof(people.height)) fmt.Printf("people addr is %v\n", unsafe.Pointer(people)) fmt.Printf("height is %T\n", height) fmt.Printf("height addr is %v\n", height) println("---") // 使用類型轉(zhuǎn)換,將 unsafe.Pointer 類型的 height 轉(zhuǎn)換成 *int 指針 // 再通過(guò)最前面的 * 解引用,修改其值 身高2米26 *((*int)(height)) = 226 fmt.Println(people) // 同樣的操作可以修改年齡和名字 age := unsafe.Pointer(uintptr(unsafe.Pointer(people)) + unsafe.Offsetof(people.age)) *((*int)(age)) = 18 name := unsafe.Pointer(uintptr(unsafe.Pointer(people)) + unsafe.Offsetof(people.name)) *((*string)(name)) = "小明" fmt.Println(people)
輸出
people: &{0 0 }
people addr is 0x1400005e020
height is unsafe.Pointer
height addr is 0x1400005e028
---
people: &{0 226 }
people: &{18 226 }
people: &{18 226 小明}
通過(guò)指針轉(zhuǎn)換將一個(gè)字節(jié)切片轉(zhuǎn)成浮點(diǎn)數(shù)組
再看一個(gè)操作,通過(guò)指針轉(zhuǎn)換,將一個(gè)字節(jié)切片轉(zhuǎn)成浮點(diǎn)數(shù)組:
package main import ( "fmt" "unsafe" ) func main() { // 假設(shè)我們有一個(gè)字節(jié)切片,并且我們知道它是由浮點(diǎn)數(shù)表示的 byteSlice := []byte{0, 0, 0, 0, 0, 0, 240, 63} // 1.0 的 IEEE-754 表示 // 使用 unsafe 把字節(jié)切片轉(zhuǎn)換為浮點(diǎn)數(shù)切片 floatSlice := (*[1]float64)(unsafe.Pointer(&byteSlice[0])) fmt.Println(floatSlice) }
輸出
&[1]
這個(gè)過(guò)程不需要 Go 的類型檢查,繞過(guò)了很多流程,相對(duì)來(lái)說(shuō)性能會(huì)更高。
所以大體上通過(guò) unsafe.Pointer 的指針運(yùn)算會(huì)應(yīng)用在如下幾個(gè)方面:
- 性能優(yōu)化: 當(dāng)性能是關(guān)鍵因素時(shí),
unsafe
可以用來(lái)避免一些開(kāi)銷。例如,通過(guò)直接操作內(nèi)存,可以避免切片或數(shù)組的額外分配和復(fù)制。 - C 語(yǔ)言交互: 當(dāng)使用 cgo 與 C 語(yǔ)言庫(kù)交互時(shí),
unsafe
包通常用于轉(zhuǎn)換類型和指針。 - 自定義序列化/反序列化: 在自定義的序列化或反序列化邏輯中,
unsafe
可以用于直接訪問(wèn)結(jié)構(gòu)的內(nèi)存布局,可以提高性能。 - 實(shí)現(xiàn)非標(biāo)準(zhǔn)的數(shù)據(jù)結(jié)構(gòu): 有時(shí),特定的問(wèn)題需要非標(biāo)準(zhǔn)的數(shù)據(jù)結(jié)構(gòu)。
unsafe
允許你直接操作內(nèi)存,可以用來(lái)實(shí)現(xiàn)一些 Go 的標(biāo)準(zhǔn)庫(kù)中沒(méi)有的數(shù)據(jù)結(jié)構(gòu)。 - 反射: 與反射結(jié)合時(shí),
unsafe
可以用于訪問(wèn)結(jié)構(gòu)體的私有字段。
以上就是Go語(yǔ)言普通指針unsafe.Pointer uintpt之間的關(guān)系及指針運(yùn)算的詳細(xì)內(nèi)容,更多關(guān)于Go普通指針unsafe.Pointer uintptr的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go 1.22對(duì)net/http包的路由增強(qiáng)功能詳解
Go 1.22 版本對(duì) net/http 包的路由功能進(jìn)行了增強(qiáng),引入了方法匹配(method matching)和通配符(wildcards)兩項(xiàng)新功能,本文將給大家詳細(xì)的介紹一下Go 1.22對(duì)net/http包的路由增強(qiáng)功能,需要的朋友可以參考下2024-02-02關(guān)于go-zero服務(wù)自動(dòng)收集問(wèn)題分析
這篇文章主要介紹了關(guān)于go-zero服務(wù)自動(dòng)收集問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-12-12Go語(yǔ)言執(zhí)行系統(tǒng)命令行命令的方法
這篇文章主要介紹了Go語(yǔ)言執(zhí)行系統(tǒng)命令行命令的方法,實(shí)例分析了Go語(yǔ)言操作系統(tǒng)命令行的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02GPT回答go語(yǔ)言和C語(yǔ)言map操作方法對(duì)比
這篇文章主要為大家介紹了GPT回答go語(yǔ)言和C語(yǔ)言map操作方法對(duì)比,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10golang 并發(fā)編程之生產(chǎn)者消費(fèi)者詳解
這篇文章主要介紹了golang 并發(fā)編程之生產(chǎn)者消費(fèi)者詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05Golang實(shí)現(xiàn)根據(jù)某個(gè)特定字段對(duì)結(jié)構(gòu)體的順序進(jìn)行排序
這篇文章主要為大家詳細(xì)介紹了Golang如何實(shí)現(xiàn)根據(jù)某個(gè)特定字段對(duì)結(jié)構(gòu)體的順序進(jìn)行排序,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03詳解go語(yǔ)言是如何實(shí)現(xiàn)協(xié)程的
go語(yǔ)言的精華就在于協(xié)程的設(shè)計(jì),只有理解協(xié)程的設(shè)計(jì)思想和工作機(jī)制,才能確保我們能夠完全的利用協(xié)程編寫(xiě)強(qiáng)大的并發(fā)程序,所以本文將給大家介紹了go語(yǔ)言是如何實(shí)現(xiàn)協(xié)程的,文中有詳細(xì)的代碼講解,需要的朋友可以參考下2024-04-04