Skip to content

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

go
type ArbitraryType int

Arbitrary 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

go
type IntegerType int

IntegerType 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

go
func Sizeof(x ArbitraryType) uintptr

Mengembalikan ukuran variabel x dalam byte, tidak termasuk ukuran konten yang direferensikannya, misalnya:

go
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
16

Offsetof

go
func Offsetof(x ArbitraryType) uintptr

Fungsi 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

go
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
8

Alignof

Jika tidak mengerti apa itu alignment memori, dapat pergi ke: Go 语言内存对齐详解 - 掘金 (juejin.cn)

go
func Alignof(x ArbitraryType) uintptr

Ukuran 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:

go
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))
}
go
8 24
8 16
4 4

Pointer

go
type Pointer *ArbitraryType

Pointer 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.Pointer dapat dikonversi ke pointer tipe apa pun
  • uintptr dapat dikonversi ke unsafe.Pointer
  • unsafe.Pointer dapat dikonversi ke uintptr

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:

go
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.3

Dua 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

go
func main() {
   num := 1
   fmt.Println(unsafe.Pointer(&num))
   fmt.Printf("0x%x", uintptr(unsafe.Pointer(&num)))
}
0xc00001c088
0xc00001c088

Perbedaan 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:

go
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 dereference

Jadi 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.

go
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

go
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
2

Add

go
func Add(ptr Pointer, len IntegerType) Pointer

Add akan mengembalikan Pointer yang diperbarui dengan offset len, setara dengan Pointer(uintptr(ptr) + uintptr(len))

go
Pointer(uintptr(ptr) + uintptr(len))

Misalnya:

go
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
3

SliceData

go
func SliceData(slice []ArbitraryType) *ArbitraryType

Fungsi 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

go
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

go
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

go
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType

Fungsi 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.

go
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

go
func StringData(str string) *byte

Sama dengan fungsi SliceData, hanya karena konversi string ke slice byte cukup sering, jadi diambil terpisah, contoh penggunaan adalah sebagai berikut

go
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

go
func String(ptr *byte, len IntegerType) string

Sama 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

go
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.

Golang by www.golangdev.cn edit