unsafe
Alamat dokumentasi resmi: unsafe package - unsafe - Go Packages
Pustaka standar unsafe adalah pustaka yang disediakan resmi untuk pemrograman tingkat rendah, operasi yang disediakan package ini dapat melewati sistem tipe Go untuk membaca dan menulis memori secara langsung. Package ini mungkin tidak portabel, dan官方 menyatakan bahwa package ini tidak dilindungi oleh kriteria kompatibilitas Go 1. Meskipun demikian, unsafe masih banyak digunakan oleh banyak proyek, termasuk juga pustaka standar yang disediakan resmi.
TIP
Alasan mengapa tidak portabel adalah karena hasil beberapa operasi bergantung pada implementasi sistem operasi, sistem yang berbeda mungkin memiliki hasil yang berbeda.
ArbitraryType
type ArbitraryType intArbitrary dapat diterjemahkan sebagai sembarang, di sini mewakili tipe sembarang, dan tidak sama dengan any, sebenarnya tipe ini tidak termasuk dalam package unsafe, muncul di sini hanya untuk tujuan dokumentasi.
IntegerType
type IntegerType intIntegerType mewakili tipe integer sembarang, sebenarnya tipe ini tidak termasuk dalam package unsafe, muncul di sini hanya untuk tujuan dokumentasi.
Dua tipe di atas tidak perlu terlalu diperhatikan, mereka hanya mewakili saja, saat menggunakan fungsi package unsafe editor bahkan akan memberi tahu Anda bahwa tipe tidak cocok, tipe aktual mereka adalah tipe konkret yang Anda masukkan.
Sizeof
func Sizeof(x ArbitraryType) uintptrMengembalikan ukuran variabel x dalam byte, tidak termasuk ukuran konten yang direferensikannya, misalnya:
func main() {
var ints byte = 1
fmt.Println(unsafe.Sizeof(ints))
var floats float32 = 1.0
fmt.Println(unsafe.Sizeof(floats))
var complexs complex128 = 1 + 2i
fmt.Println(unsafe.Sizeof(complexs))
var slice []int = make([]int, 100)
fmt.Println(unsafe.Sizeof(slice))
var mp map[string]int = make(map[string]int, 0)
fmt.Println(unsafe.Sizeof(mp))
type person struct {
name string
age int
}
fmt.Println(unsafe.Sizeof(person{}))
type man struct {
name string
}
fmt.Println(unsafe.Sizeof(man{}))
}1
4
16
24
8
24
16Offsetof
func Offsetof(x ArbitraryType) uintptrFungsi ini digunakan untuk menunjukkan offset field dalam struct, jadi x harus berupa field struct, atau nilai kembalian adalah jumlah byte antara alamat awal struct dan alamat awal field, misalnya
func main() {
type person struct {
name string
age int
}
p := person{
name: "aa",
age: 11,
}
fmt.Println(unsafe.Sizeof(p))
fmt.Println(unsafe.Offsetof(p.name))
fmt.Println(unsafe.Sizeof(p.name))
fmt.Println(unsafe.Offsetof(p.age))
fmt.Println(unsafe.Sizeof(p.age))
}24
0
16
16
8Alignof
Jika tidak mengerti apa itu alignment memori, dapat pergi ke: Go 语言内存对齐详解 - 掘金 (juejin.cn)
func Alignof(x ArbitraryType) uintptrUkuran alignment biasanya adalah panjang kata komputer dalam byte dan nilai minimum Sizeof, misalnya pada mesin amd64, panjang kata adalah 64 bit, yaitu 8 byte, misalnya:
func main() {
type person struct {
name string
age int32
}
p := person{
name: "aa",
age: 11,
}
fmt.Println(unsafe.Alignof(p), unsafe.Sizeof(p))
fmt.Println(unsafe.Alignof(p.name), unsafe.Sizeof(p.name))
fmt.Println(unsafe.Alignof(p.age), unsafe.Sizeof(p.age))
}8 24
8 16
4 4Pointer
type Pointer *ArbitraryTypePointer adalah "pointer" yang dapat menunjuk ke tipe sembarang, tipenya adalah *ArbitraryType, tipe ini digunakan bersama uintptr, baru dapat benar-benar menunjukkan kekuatan sebenarnya package unsafe. Dalam deskripsi dokumentasi resmi, tipe unsafe.Pointer dapat melakukan empat operasi khusus, yaitu:
- Pointer tipe apa pun dapat dikonversi ke
unsafe.Pointer unsafe.Pointerdapat dikonversi ke pointer tipe apa punuintptrdapat dikonversi keunsafe.Pointerunsafe.Pointerdapat dikonversi keuintptr
Keempat operasi khusus ini membentuk batu dasar seluruh package unsafe, dan juga dengan keempat operasi ini baru dapat menulis kode yang dapat mengabaikan sistem tipe untuk membaca dan menulis memori secara langsung, disarankan agar sangat berhati-hati saat menggunakannya.
TIP
unsafe.Pointer tidak dapat di-dereference, sama juga tidak dapat diambil alamatnya.
(1) Mengonversi *T1 ke unsafe.Pointer lalu mengonversi ke *T2
Ada tipe *T1, *T2, asumsikan T2 tidak lebih besar dari T1 dan layout memori keduanya setara, maka diperbolehkan mengonversi data tipe T2 ke T1. Misalnya:
func main() {
fmt.Println(Float64bits(12.3))
fmt.Println(Float64frombits(Float64bits(12.3)))
}
func Float64bits(f float64) uint64 {
return *(*uint64)(unsafe.Pointer(&f))
}
func Float64frombits(b uint64) float64 {
return *(*float64)(unsafe.Pointer(&b))
}4623113902481840538
12.3Dua fungsi ini sebenarnya adalah dua fungsi di bawah package math, perubahan tipe dalam prosesnya adalah sebagai berikut
float64 -> *float64 -> unsafe.Pointer -> *uint64 -> uint64 -> *uint64 -> unsafe.Pointer -> *float64 -> float64(2) Mengonversi unsafe.Pointer ke uintptr
Saat mengonversi unsafe.Pointer ke uintptr, akan mengubah alamat yang ditunjuk oleh yang pertama sebagai nilai yang terakhir, uintptr menyimpan alamat, perbedaannya adalah, yang pertama secara sintaksis adalah pointer, adalah referensi, yang terakhir hanya nilai integer. Misalnya
func main() {
num := 1
fmt.Println(unsafe.Pointer(&num))
fmt.Printf("0x%x", uintptr(unsafe.Pointer(&num)))
}0xc00001c088
0xc00001c088Perbedaan yang lebih besar adalah dalam penanganan garbage collection, karena unsafe.Pointer adalah referensi, saat diperlukan tidak akan di-recycle, sedangkan yang terakhir hanya sebagai nilai, tentu tidak akan mendapat perlakuan khusus ini, poin lain yang perlu diperhatikan adalah ketika alamat elemen yang ditunjuk oleh pointer bergerak, GC akan memperbarui alamat lama yang direferensikan pointer, tetapi tidak akan memperbarui nilai yang disimpan oleh uintptr. Misalnya kode berikut mungkin bermasalah:
func main() {
num := 16
address := uintptr(unsafe.Pointer(&num))
np := (*int64)(unsafe.Pointer(address))
fmt.Println(*np)
}Dalam beberapa kasus, setelah GC memindahkan variabel, alamat yang ditunjuk oleh address sudah tidak valid, saat ini menggunakan nilai tersebut untuk membuat pointer akan menyebabkan panic
panic: runtime error: invalid memory address or nil pointer dereferenceJadi tidak disarankan menyimpan nilai setelah Pointer dikonversi ke uintptr.
(3) Mengonversi uintptr ke unsafe.Pointer
Cara berikut dapat memperoleh pointer dari uintptr, selama pointer valid, maka tidak akan terjadi situasi alamat tidak valid seperti contoh kedua. Pointer dan pointer tipe itu sendiri tidak mendukung operasi pointer, tetapi uintptr hanya nilai integer, dapat melakukan operasi matematika, melakukan operasi matematika pada uintptr lalu mengonversi ke Pointer dapat menyelesaikan operasi pointer.
p = unsafe.Pointer(uintptr(p) + offset)Dengan demikian, hanya melalui satu pointer, dapat mengakses beberapa elemen internal tipe, seperti array dan struct, terlepas dari apakah elemen internalnya diekspos ke luar atau tidak, misalnya
func main() {
type person struct {
name string
age int32
}
p := &person{"jack", 18}
pp := unsafe.Pointer(p)
fmt.Println(*(*string)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(p.name))))
fmt.Println(*(*int32)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(p.age))))
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
ps := unsafe.Pointer(&s[0])
fmt.Println(*(*int)(unsafe.Pointer(uintptr(ps) + 8)))
fmt.Println(*(*int)(unsafe.Pointer(uintptr(ps) + 16)))
}jack
18
2Add
func Add(ptr Pointer, len IntegerType) PointerAdd akan mengembalikan Pointer yang diperbarui dengan offset len, setara dengan Pointer(uintptr(ptr) + uintptr(len))
Pointer(uintptr(ptr) + uintptr(len))Misalnya:
func main() {
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
ps := unsafe.Pointer(&s[0])
fmt.Println(*(*int)(unsafe.Add(ps, 8)))
fmt.Println(*(*int)(unsafe.Add(ps, 16)))
}2
3SliceData
func SliceData(slice []ArbitraryType) *ArbitraryTypeFungsi ini menerima slice, mengembalikan alamat awal array dasarnya. Jika tidak menggunakan SliceData, maka hanya dapat memperoleh alamat array dasar dengan mengambil pointer elemen pertamanya, seperti berikut
func main() {
nums := []int{1, 2, 3, 4}
for p, i := unsafe.Pointer(&nums[0]), 0; i < len(nums); p, i = unsafe.Add(p, unsafe.Sizeof(nums[0])), i+1 {
num := *(*int)(p)
fmt.Println(num)
}
}Tentu juga dapat memperolehnya melalui tipe reflect.SliceHeader, tetapi setelah versi 1.20 sudah di-deprecated, SliceData adalah untuk menggantikannya, contoh penggunaan SliceData adalah sebagai berikut
func main() {
nums := []int{1, 2, 3, 4}
for p, i := unsafe.Pointer(unsafe.SliceData(nums)), 0; i < len(nums); p, i = unsafe.Add(p, unsafe.Sizeof(int(0))), i+1 {
num := *(*int)(p)
fmt.Println(num)
}
}Slice
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryTypeFungsi Slice menerima pointer, dan offset panjang, akan mengembalikan bentuk ekspresi slice dari segmen memori tersebut, prosesnya tidak melibatkan penyalinan memori, memodifikasi slice akan langsung mempengaruhi data pada alamat tersebut, sebaliknya juga demikian, biasanya digunakan bersama SliceData.
func main() {
nums := []int{1, 2, 3, 4}
numsRef1 := unsafe.Slice(unsafe.SliceData(nums), len(nums))
numsRef1[0] = 2
fmt.Println(nums)
}[2 2 3 4]Memodifikasi data slice numsRef1, akan menyebabkan data nums juga berubah
StringData
func StringData(str string) *byteSama dengan fungsi SliceData, hanya karena konversi string ke slice byte cukup sering, jadi diambil terpisah, contoh penggunaan adalah sebagai berikut
func main() {
str := "hello,world!"
for ptr, i := unsafe.Pointer(unsafe.StringData(str)), 0; i < len(str); ptr, i = unsafe.Add(ptr, unsafe.Sizeof(byte(0))), i+1 {
char := *(*byte)(ptr)
fmt.Println(string(char))
}
}Karena literal string disimpan di segmen read-only dalam proses, jadi jika Anda mencoba memodifikasi data底层 string di sini, program akan langsung crash dengan fatal. Namun untuk string variabel yang disimpan di heap/stack, memodifikasi data dasarnya saat runtime sepenuhnya可行.
String
func String(ptr *byte, len IntegerType) stringSama dengan fungsi Slice, menerima pointer tipe byte, dan offset panjangnya, mengembalikan bentuk ekspresi stringnya, prosesnya tidak melibatkan penyalinan memori. Berikut adalah contoh konversi slice byte ke string
func main() {
bytes := []byte("hello world")
str := unsafe.String(unsafe.SliceData(bytes), len(bytes))
fmt.Println(str)
}StringData dan String tidak melibatkan penyalinan memori dalam proses konversi string dengan slice byte, kinerjanya lebih baik daripada konversi tipe langsung, tetapi hanya cocok untuk kasus read-only, jika Anda berencana memodifikasi data, sebaiknya jangan menggunakan ini.
