Skip to content

unsafe

Resmi dokümantasyon adresi: unsafe package - unsafe - Go Packages

unsafe standart kütüphanesi resmi olarak sağlanan düşük seviyeli programlama yapabileceğiniz bir kütüphanedir, bu paketin sağladığı işlemler Go tip sistemini atlayarak doğrudan bellek okuma ve yazma işlemleri yapabilir. Bu paket taşınabilir olmayabilir ve resmi olarak bu paketin Go 1 uyumluluk kriterlerinden korunmadığı belirtilmiştir. Buna rağmen, unsafe hala çok sayıda projede kullanılır, bunlar arasında resmi olarak sağlanan standart kütüphane de bulunmaktadır.

TIP

Taşınamaz olmasının nedeni bazı işlemlerin sonuçlarının işletim sistemi implementasyonuna bağlı olmasıdır, farklı sistemler farklı sonuçlar verebilir.

ArbitraryType

go
type ArbitraryType int

Arbitrary herhangi olarak çevrilebilir, burada herhangi bir tipi temsil eder ve any ile eşdeğer değildir, aslında bu tip unsafe paketine ait değildir, burada sadece dokümantasyon amaçlı olarak yer almaktadır.

IntegerType

go
type IntegerType int

IntegerType herhangi bir tamsayı tipini temsil eder, aslında bu tip unsafe paketine ait değildir, burada sadece dokümantasyon amaçlı olarak yer almaktadır.

Yukarıdaki iki tip için çok fazla endişelenmenize gerek yok, onlar sadece bir temsilcidir, unsafe paketi fonksiyonlarını kullanırken editör tip uyuşmazlığı uyarısı verecektir, gerçek tipleri sizin girdiğiniz somut tiptir.

Sizeof

go
func Sizeof(x ArbitraryType) uintptr

Değişken x'in boyutunu bayt cinsinden döndürür, referans içeriğinin boyutunu içermez, örneğin:

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

Bu fonksiyon struct içindeki alan ofsetini temsil etmek için kullanılır, bu nedenle x bir struct alanı olmalıdır, veya dönüş değeri struct adres başlangıcı ile alan adres başlangıcı arasındaki bayt sayısıdır, örneğin

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

Bellek hizalamanın ne olduğunu bilmiyorsanız, buraya bakabilirsiniz: Go Dili Bellek Hizalama Detaylı Açıklama

go
func Alignof(x ArbitraryType) uintptr

Hizalama boyutu genellikle bayt cinsinden bilgisayar kelime uzunluğu ile Sizeof'un minimum değeridir, örneğin amd64 makinesinde, kelime uzunluğu 64 bit, yani 8 bayttır, örneğin:

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 herhangi bir tipe işaret edebilen bir "işaretçi"dir, tipi *ArbitraryType'dır, bu tip uintptr ile birlikte kullanıldığında gerçekten unsafe paketinin gücünü ortaya çıkarabilir. Resmi dokümantasyon açıklamasında, unsafe.Pointer tipi dört özel işlem yapabilir, bunlar:

  • Herhangi bir tip işaretçi unsafe.Pointer'a dönüştürülebilir
  • unsafe.Pointer herhangi bir tip işaretçiye dönüştürülebilir
  • uintptr unsafe.Pointer'a dönüştürülebilir
  • unsafe.Pointer uintptr'e dönüştürülebilir

Bu dört özel işlem tüm unsafe paketinin temelini oluşturur ve bu dört işlem sayesinde tip sistemini görmezden gelerek doğrudan bellek okuma ve yazma kodu yazılabilir, kullanırken özellikle dikkat edilmesi önerilir.

TIP

unsafe.Pointer referans alınarak çözümlenemez, aynı şekilde adres de alınamaz.

(1) *T1'i unsafe.Pointer'a sonra *T2'ye dönüştürme

Mevcut tip *T1, *T2, T2'nin T1'den büyük olmadığı ve her ikisinin bellek düzeni eşdeğer olduğu varsayıldığında, bir T2 tipi verinin T1'e dönüştürülmesine izin verilir. Örneğin:

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

Bu iki fonksiyon aslında math paketindeki iki fonksiyondur, süreçteki tip değişimleri aşağıdaki gibidir

float64 -> *float64 -> unsafe.Pointer ->  *uint64 -> uint64 -> *uint64 -> unsafe.Pointer -> *float64 -> float64

(2) unsafe.Pointeruintptr'e dönüştürme

unsafe.Pointeruintptr'e dönüştürürken, ilkinin işaret ettiği adres ikincinin değeri olarak kullanılır, uintptr adresi saklar, fark şudur, ilki sözdizimsel olarak bir işaretçidir, bir referanstır, ikincisi sadece bir tamsayı değeridir. Örneğin

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

Daha büyük fark çöp toplama işlemededir, unsafe.Pointer bir referans olduğundan, gerektiğinde geri alınmaz, ikincisi sadece bir değer olarak doğal olarak bu özel muameleyi görmez, diğer dikkat edilmesi gereken nokta işaretçinin işaret ettiği eleman adresi hareket ettiğinde, GC işaretçinin referans ettiği eski adresi günceller, ancak uintptr'in sakladığı değeri güncellemez. Örneğin aşağıdaki kod sorun yaratabilir:

go
func main() {
   num := 16
   address := uintptr(unsafe.Pointer(&num))
   np := (*int64)(unsafe.Pointer(address))
   fmt.Println(*np)
}

Bazı durumlarda, GC değişkeni taşıdıktan sonra, address'in işaret ettiği adres geçersiz hale gelir, bu değeri kullanarak işaretçi oluşturmak panic'e neden olur

panic: runtime error: invalid memory address or nil pointer dereference

Bu nedenle Pointer'ın uintptr'e dönüştürüldükten sonraki değerini saklamanız önerilmez.

(3) uintptr'i unsafe.Pointer'a dönüştürme

Aşağıdaki şekilde uintptr'den bir işaretçi elde edilebilir, işaretçi geçerli olduğu sürece, örnek iki geçersiz adres durumu oluşmaz. Pointer ve tip işaretçisi kendisi işaretçi işlemini desteklemez, ancak uintptr sadece bir tamsayı değeridir, matematiksel işlem yapılabilir, uintptr'e matematiksel işlem yapıldıktan sonra Pointer'a dönüştürülerek işaretçi işlemi tamamlanabilir.

go
p = unsafe.Pointer(uintptr(p) + offset)

Böylece, sadece bir işaretçi kullanarak bazı tiplerin dahili elemanlarına erişilebilir, örneğin dizi ve struct, dahili elemanların dışa açık olup olmadığına bakılmaksızın, örneğin

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, len ofseti ile güncellenmiş Pointer'ı döndürür, Pointer(uintptr(ptr) + uintptr(len)) ile eşdeğerdir

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

Örneğin:

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

Bu fonksiyon bir slice alır, alt dizisinin başlangıç adresini döndürür. SliceData kullanmazsanız, ilk elemanın işaretçisini alarak alt dizinin adresini elde edebilirsiniz, aşağıdaki gibi

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)
  }
}

Elbette reflect.SliceHeader tipi ile de elde edebilirsiniz, ancak 1.20 sürümünden sonra bu kullanımdan kaldırılmıştır, SliceData onun yerine geçmek içindir, SliceData kullanan örnek aşağıdadır

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

Slice fonksiyonu bir işaretçi ve uzunluk ofseti alır, bu bellek bölümünün slice ifade formunu döndürür, süreçte bellek kopyalama içermez, slice üzerinde değişiklik yapmak bu adresteki veriyi doğrudan etkiler, tersi de geçerlidir, genellikle SliceData ile birlikte kullanılır.

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]

numsRef1 slice verisini değiştirmek, nums verisinin de değişmesine neden olur

StringData

go
func StringData(str string) *byte

SliceData fonksiyonu ile aynı, ancak string'den byte slice'a dönüşüm ihtiyacı sık olduğundan, ayrı olarak alınmıştır, kullanım örneği aşağıdadır

go
func main() {
  str := "merhaba,dünya!"
  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))
  }
}

String literal işlemin salt okunur bölümünde saklandığından, burada string alt verisini değiştirmeye çalışırsanız, program doğrudan fatal hatası ile çöker. Ancak yığın üzerinde saklanan string değişkenleri için, çalışma zamanında alt verilerini değiştirmek tamamen mümkündür.

String

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

Slice fonksiyonu ile aynı, bir byte tip işaretçi ve uzunluk ofseti alır, string ifade formunu döndürür, süreçte bellek kopyalama içermez. Aşağıda byte slice'tan string'e dönüşüm örneği vardır

go
func main() {
  bytes := []byte("merhaba dünya")
  str := unsafe.String(unsafe.SliceData(bytes), len(bytes))
  fmt.Println(str)
}

StringData ve String string ile byte slice dönüşüm sürecinde bellek kopyalama içermez, performans doğrudan tip dönüşümünden daha iyidir, ancak sadece salt okunur durumlar için geçerlidir, veriyi değiştirmeyi planlıyorsanız, bunu kullanmamanız daha iyidir.

Golang by www.golangdev.cn edit