Go語言普通指針unsafe.Pointer?uintpt之間的關(guān)系及指針運(yùn)算
C 語言指針運(yùn)算
指針運(yùn)算就是對指針類型的變量做常規(guī)數(shù)學(xué)運(yùn)算,例如加減操作,實(shí)現(xiàn)地址的偏移。指針運(yùn)算在 C 語言中是原生支持的,可以直接在指針變量上做加減,例如:
#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("存儲地址:var[%d] = %p\n", i, ptr ); printf("存儲值:var[%d] = %d\n", i, *ptr ); /* 直接對指針做++操作,指向下一個位置 */ ptr++; } return 0; }
結(jié)果
存儲地址:var[0] = e4a298cc
存儲值:var[0] = 10
存儲地址:var[1] = e4a298d0
存儲值:var[1] = 100
存儲地址:var[2] = e4a298d4
存儲值:var[2] = 200
C 語言指針運(yùn)算猶如一把雙刃劍,使用得當(dāng)會起到事半功倍,有神之一手的效果,反之則會產(chǎn)生意想不到的 bug 而且很難排查。因?yàn)樵谧鲋羔樳\(yùn)算時是比較抽象的,具體偏移了多少之后指向到了哪里是非常不直觀的,可能已經(jīng)偏離了設(shè)想中的位置而沒有發(fā)現(xiàn),運(yùn)行起來就會出現(xiàn)錯誤。
找出數(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 語言的數(shù)組實(shí)際上就是指針,C 運(yùn)行時認(rèn)為這是在指針運(yùn)算,所以不會報錯,導(dǎo)致數(shù)組訪問到了其他內(nèi)存地址,最終得到了一個錯誤結(jié)果。
事實(shí)上有很多病毒和外掛的原理就是利用指針來訪問并修改程序運(yùn)行時內(nèi)存數(shù)據(jù)來達(dá)到目的。例如游戲外掛可能會搜索和修改內(nèi)存中的特定值,以改變玩家的生命值、金錢或其他游戲?qū)傩?。通過指針運(yùn)算,外掛可以直接訪問這些內(nèi)存位置并對其進(jìn)行修改。而病毒可能使用指針運(yùn)算來插入其自己的代碼到一個運(yùn)行中的程序,或者篡改程序的正??刂屏?,以達(dá)到其惡意目的。
在 C 語言之后的很多語言多多少少都對指針做了限制,例如 PHP 中的引用就可以看做是指針的簡化版,而 Java 甚至干脆移除了指針。
Go 指針運(yùn)算
對指針做加法會報錯
在 Go 中默認(rèn)的普通指針也是指代的是一個內(nèi)存地址,值類似 0x140000ac008
,但 Go 的普通指針不支持指針運(yùn)算的,例如對指針做加法會報錯:
a := 10 var p *int = &a p = p + 1
報錯
invalid operation: p + 1 (mismatched types *int and untyped int)
但 Go 還是提供了一種直接操作指針的方式,就是 unsafe.Pointer 和 uintptr。
uintptr 是一個整型,可理解為是將內(nèi)存地址轉(zhuǎn)換成了一個整數(shù),既然是一個整數(shù),就可以對其做數(shù)值計算,實(shí)現(xiàn)指針地址的加減,也就是地址偏移,類似跟 C 語言中一樣的效果。
而 unsafe.Pointer 是普通指針和 uintptr 之間的橋梁,通過 unsafe.Pointer 實(shí)現(xiàn)三者的相互轉(zhuǎn)換。
*T <-> unsafe.Pointer <-> uintptr
先看看這三位都長什么樣:
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
舉一個通過指針運(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 字段相對于結(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 指針 // 再通過最前面的 * 解引用,修改其值 身高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 小明}
通過指針轉(zhuǎn)換將一個字節(jié)切片轉(zhuǎn)成浮點(diǎn)數(shù)組
再看一個操作,通過指針轉(zhuǎn)換,將一個字節(jié)切片轉(zhuǎn)成浮點(diǎn)數(shù)組:
package main import ( "fmt" "unsafe" ) func main() { // 假設(shè)我們有一個字節(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]
這個過程不需要 Go 的類型檢查,繞過了很多流程,相對來說性能會更高。
所以大體上通過 unsafe.Pointer 的指針運(yùn)算會應(yīng)用在如下幾個方面:
- 性能優(yōu)化: 當(dāng)性能是關(guān)鍵因素時,
unsafe
可以用來避免一些開銷。例如,通過直接操作內(nèi)存,可以避免切片或數(shù)組的額外分配和復(fù)制。 - C 語言交互: 當(dāng)使用 cgo 與 C 語言庫交互時,
unsafe
包通常用于轉(zhuǎn)換類型和指針。 - 自定義序列化/反序列化: 在自定義的序列化或反序列化邏輯中,
unsafe
可以用于直接訪問結(jié)構(gòu)的內(nèi)存布局,可以提高性能。 - 實(shí)現(xiàn)非標(biāo)準(zhǔn)的數(shù)據(jù)結(jié)構(gòu): 有時,特定的問題需要非標(biāo)準(zhǔn)的數(shù)據(jù)結(jié)構(gòu)。
unsafe
允許你直接操作內(nèi)存,可以用來實(shí)現(xiàn)一些 Go 的標(biāo)準(zhǔn)庫中沒有的數(shù)據(jù)結(jié)構(gòu)。 - 反射: 與反射結(jié)合時,
unsafe
可以用于訪問結(jié)構(gòu)體的私有字段。
以上就是Go語言普通指針unsafe.Pointer uintpt之間的關(guān)系及指針運(yùn)算的詳細(xì)內(nèi)容,更多關(guān)于Go普通指針unsafe.Pointer uintptr的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用Golang實(shí)現(xiàn)對網(wǎng)絡(luò)數(shù)據(jù)包的捕獲與分析
在網(wǎng)絡(luò)通信中,網(wǎng)絡(luò)數(shù)據(jù)包是信息傳遞的基本單位,抓包是一種監(jiān)控和分析網(wǎng)絡(luò)流量的方法,用于獲取網(wǎng)絡(luò)數(shù)據(jù)包并對其進(jìn)行分析,本文將介紹如何使用Golang實(shí)現(xiàn)抓包功能,包括網(wǎng)絡(luò)數(shù)據(jù)包捕獲和數(shù)據(jù)包分析,需要的朋友可以參考下2023-11-11golang類型轉(zhuǎn)換之interface轉(zhuǎn)字符串string簡單示例
在我們使用Golang進(jìn)行開發(fā)過程中,總是繞不開對字符或字符串的處理,這篇文章主要給大家介紹了關(guān)于golang類型轉(zhuǎn)換之interface轉(zhuǎn)字符串string的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01Go語言基礎(chǔ)函數(shù)包的使用學(xué)習(xí)
本文通過一個實(shí)現(xiàn)加減乘除運(yùn)算的小程序來介紹go函數(shù)的使用,以及使用函數(shù)的注意事項(xiàng),并引出了對包的了解和使用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05goFrame的隊(duì)列g(shù)queue對比channel使用詳解
這篇文章主要為大家介紹了goFrame的gqueue對比channel使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06