golang如何使用指針靈活操作內(nèi)存及unsafe包原理解析
Hi 你好,我是k哥。一個(gè)大廠工作6年,還在繼續(xù)搬磚的后端程序員。
我們都知道,C/C++提供了強(qiáng)大的萬能指針void*,任何類型的指針都可以和萬能指針相互轉(zhuǎn)換。并且指針還可以進(jìn)行加減等算數(shù)操作。那么在Golang中,是否有類似的功能呢?答案是有的,這就是我們今天要探討的unsafe包。
本文將深入探討unsafe包的功能和原理。同時(shí),我們學(xué)習(xí)某種東西,一方面是為了實(shí)踐運(yùn)用,另一方面則是出于功利性面試的目的。所以,本文還會(huì)為大家介紹unsafe 包的典型應(yīng)用以及高頻面試題。
功能
為了實(shí)現(xiàn)靈活操作內(nèi)存的目的,unsafe包主要提供了4個(gè)功能:
- 定義了Pointer類型,任何類型的指針都可和Pointer互相轉(zhuǎn)換,類似于c語言中的void*
var a int = 1 p := unsafe.Pointer(&a) // 其它類型指針轉(zhuǎn)Pointer b := (*int)(p) // Pointer類型轉(zhuǎn)其它類型指針 fmt.Println(*b) // 輸出1
- 定義了uintptr類型,Pointer和uintptr可以互相轉(zhuǎn)換, 從而實(shí)現(xiàn)指針的加減等算數(shù)運(yùn)算。
type Person struct {
age int
name string
}
person := Person{age:18,name:"k哥"}
p := unsafe.Pointer(&person) // 其它類型指針轉(zhuǎn)Pointer
u := uintptr(p) // Pointer類型轉(zhuǎn)為uintptr
u=u+8 // uintptr加減操作
pName := unsafe.Pointer(u) // uintptr轉(zhuǎn)換為Pointer
name := *(*string)(pName)
fmt.Println(name) // 輸出k哥uintptr是用于指針運(yùn)算的,它只是一個(gè)存儲(chǔ)一個(gè) 指針地址 的 int 類型,GC 不把 uintptr 當(dāng)指針,因此, uintptr 類型的目標(biāo)可能會(huì)被回收
- 獲取任意類型內(nèi)存對(duì)齊、偏移量和內(nèi)存大小。
func Alignof(x ArbitraryType) uintptr // 內(nèi)存對(duì)齊 func Offsetof(x ArbitraryType) uintptr // 內(nèi)存偏移量 func Sizeof(x ArbitraryType) uintptr // 內(nèi)存大小
- Alignof 返回類型x的內(nèi)存地址對(duì)齊值m,這個(gè)類型在內(nèi)存中的地址必須是m的倍數(shù)(基于內(nèi)存讀寫性能的考慮)。
- Offsetof 返回結(jié)構(gòu)體成員x在內(nèi)存中的位置離結(jié)構(gòu)體起始處(結(jié)構(gòu)體的第一個(gè)字段的偏移量都是0)的字節(jié)數(shù),即偏移量。
- Sizeof 返回類型 x 所占據(jù)的字節(jié)數(shù),如果類型x結(jié)構(gòu)有指針,Sizeof不包含 x 指針成員所指向內(nèi)容的大小。
ArbitraryType是占位符,golang編譯器在編譯時(shí)會(huì)替換為具體類型
- 高性能類型轉(zhuǎn)換。
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType func SliceData(slice []ArbitraryType) *ArbitraryType func String(ptr *byte, len IntegerType) string func StringData(str string) *byte
- Slice 傳入任意類型的指針和長(zhǎng)度,返回該類型slice變量
- SliceData 傳入任意類型的slice變量,返回該slice底層數(shù)組的指針。
- String 從一個(gè)byte指針派生出一個(gè)指定長(zhǎng)度的字符串。
- StringData 用來獲取一個(gè)字符串底層字節(jié)序列中的第一個(gè)byte的指針。
高性能類型轉(zhuǎn)換原理
為什么說Slice、SliceData、String、StringData是高性能類型轉(zhuǎn)換函數(shù)呢?下面我們就來剖析下它們的實(shí)現(xiàn)原理。
本文以String和StringData函數(shù)為例,Slice和SliceData函數(shù)實(shí)現(xiàn)原理類似。在介紹函數(shù)實(shí)現(xiàn)原理之前,先認(rèn)識(shí)下string類型的底層數(shù)據(jù)結(jié)構(gòu)StringHeader。string類型會(huì)被Golang編譯器編譯成此結(jié)構(gòu),其中Data是byte數(shù)組地址,Len是字符串長(zhǎng)度。
type StringHeader struct {
Data uintptr // byte數(shù)組地址
Len int // 字符串長(zhǎng)度
}String函數(shù)會(huì)被Go編譯成下面的函數(shù)實(shí)現(xiàn)邏輯。我們可以發(fā)現(xiàn),ptr指針轉(zhuǎn)換為string類型,是直接將ptr賦值給StringHeader的成員Data,而不需要重新拷貝ptr指向的byte數(shù)組。從而通過零拷貝實(shí)現(xiàn)高性能類型轉(zhuǎn)換。
import (
"fmt"
"reflect"
"unsafe"
)
func String(ptr *byte, len int) string {
p := (uintptr)(unsafe.Pointer(ptr))
hdr := &reflect.StringHeader{
Data: p,
Len: len,
}
// 將 StringHeader 轉(zhuǎn)為 string
str := *(*string)(unsafe.Pointer(hdr))
return str
}
func main() {
bytes := []byte{'h', 'e', 'l', 'l', 'o'}
ptr := &bytes[0]
len := 5
str := String(ptr, len)
fmt.Println(str) // 輸出hello
}StringData函數(shù)會(huì)被Go編譯成下面的函數(shù)實(shí)現(xiàn)邏輯。同理,我們可以發(fā)現(xiàn),string類型轉(zhuǎn)換為byte,是直接取StringHeader的uintptr類型成員Data,并將其轉(zhuǎn)換為byte。不需要拷貝整個(gè)string,重新生成byte數(shù)組。從而通過零拷貝實(shí)現(xiàn)高性能類型轉(zhuǎn)換。
import (
"fmt"
"reflect"
"unsafe"
)
func StringData(str string) *byte {
hdr := (*reflect.StringHeader)(unsafe.Pointer(&str))
data := hdr.Data
return (*byte)(unsafe.Pointer(data))
}
func main() {
str := "hello"
data := StringData(str)
fmt.Println(string(*data)) // 輸出h
}回到問題,為什么說Slice、SliceData、String、StringData是高性能類型轉(zhuǎn)換函數(shù)呢?通過String和StringData函數(shù)的實(shí)現(xiàn)邏輯,我們可以知道,String和StringData利用unsafe包,通過零拷貝,實(shí)現(xiàn)了高性能類型轉(zhuǎn)換。
典型應(yīng)用
在實(shí)踐中,常見使用unsafe包的場(chǎng)景有2個(gè):
- 與操作系統(tǒng)以及非go編寫(cgo)的代碼通信。
func SetData(bytes []byte) {
cstr := (*C.char)(unsafe.Pointer(&bytes[0])) // 轉(zhuǎn)換成一個(gè)C char類型
C.setData(cstr, (C.int)(len(bytes))) // 調(diào)用C語言函數(shù)
}- 高性能類型轉(zhuǎn)換。
func Bytes2String(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
func String2Bytes(s string) []byte {
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh := reflect.SliceHeader{
Data: sh.Data,
Len: sh.Len,
Cap: sh.Len,
}
return *(*[]byte)(unsafe.Pointer(&bh))
}高頻面試題
- 能說說uintptr和unsafe.Pointer的區(qū)別嗎?
- 字符串轉(zhuǎn)成byte數(shù)組,會(huì)發(fā)生內(nèi)存拷貝嗎?
到此這篇關(guān)于golang如何使用指針靈活操作內(nèi)存?unsafe包原理解析的文章就介紹到這了,更多相關(guān)golang unsafe包原內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Golang?container/list實(shí)現(xiàn)LRU緩存
Least?Recently?Used?(LRU)?,即逐出最早使用的緩存,這篇文章主要為大家介紹了如何基于Golang?container/list實(shí)現(xiàn)LRU緩存,感興趣的可以了解下2023-08-08
詳解Go語言中Goroutine退出機(jī)制的原理及使用
goroutine是Go語言提供的語言級(jí)別的輕量級(jí)線程,在我們需要使用并發(fā)時(shí),我們只需要通過?go?關(guān)鍵字來開啟?goroutine?即可。本文就來詳細(xì)講講Goroutine退出機(jī)制的原理及使用,感興趣的可以了解一下2022-07-07
GoLang基礎(chǔ)學(xué)習(xí)之go?test測(cè)試
相信每位編程開發(fā)者們應(yīng)該都知道,Golang作為一門標(biāo)榜工程化的語言,提供了非常簡(jiǎn)便、實(shí)用的編寫單元測(cè)試的能力,下面這篇文章主要給大家介紹了關(guān)于GoLang基礎(chǔ)學(xué)習(xí)之go?test測(cè)試的相關(guān)資料,需要的朋友可以參考下2022-08-08
使用Go語言實(shí)現(xiàn)一個(gè)簡(jiǎn)單的詞頻分析系統(tǒng)
在數(shù)據(jù)分析和文本挖掘中,詞頻統(tǒng)計(jì)(Word Frequency Analysis) 是最基礎(chǔ)也是最常用的技術(shù)之一,本文將帶你用 Go 語言實(shí)現(xiàn)一個(gè)簡(jiǎn)易的 詞頻分析系統(tǒng),感興趣的小伙伴可以了解下2025-09-09

