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
type ArbitraryType intArbitrary 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
type IntegerType intIntegerType 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
func Sizeof(x ArbitraryType) uintptrDeğişken x'in boyutunu bayt cinsinden döndürür, referans içeriğinin boyutunu içermez, örneğin:
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) uintptrBu 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
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
Bellek hizalamanın ne olduğunu bilmiyorsanız, buraya bakabilirsiniz: Go Dili Bellek Hizalama Detaylı Açıklama
func Alignof(x ArbitraryType) uintptrHizalama 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:
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 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.Pointerherhangi bir tip işaretçiye dönüştürülebiliruintptrunsafe.Pointer'a dönüştürülebilirunsafe.Pointeruintptr'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:
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.3Bu 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.Pointer'ı uintptr'e dönüştürme
unsafe.Pointer'ı uintptr'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
func main() {
num := 1
fmt.Println(unsafe.Pointer(&num))
fmt.Printf("0x%x", uintptr(unsafe.Pointer(&num)))
}0xc00001c088
0xc00001c088Daha 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:
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 dereferenceBu 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.
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
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, len ofseti ile güncellenmiş Pointer'ı döndürür, Pointer(uintptr(ptr) + uintptr(len)) ile eşdeğerdir
Pointer(uintptr(ptr) + uintptr(len))Örneğin:
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) *ArbitraryTypeBu 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
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
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) []ArbitraryTypeSlice 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.
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
func StringData(str string) *byteSliceData 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
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
func String(ptr *byte, len IntegerType) stringSlice 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
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.
