unsafe
Offizielle Dokumentation: unsafe package - unsafe - Go Packages
Die unsafe Standardbibliothek ist eine von der offiziellen Seite bereitgestellte Bibliothek für Low-Level-Programmierung. Die in diesem Paket bereitgestellten Operationen können das Go-Typsystem umgehen und direkt Speicher lesen und schreiben. Dieses Paket ist möglicherweise nicht portierbar, und die offizielle Seite erklärt, dass dieses Paket nicht den Go 1 Kompatibilitätsrichtlinien unterliegt. Dennoch wird unsafe von vielen Projekten verwendet, einschließlich der offiziellen Standardbibliothek.
TIP
Der Grund, warum es nicht portierbar ist, liegt daran, dass einige Operationsergebnisse von der Betriebssystemimplementierung abhängen. Unterschiedliche Systeme können zu unterschiedlichen Ergebnissen führen.
ArbitraryType
type ArbitraryType intArbitrary kann mit "beliebig" übersetzt werden und repräsentiert hier einen beliebigen Typ. Es ist nicht gleichbedeutend mit any. Tatsächlich gehört dieser Typ nicht zum unsafe Paket, er erscheint hier nur zu Dokumentationszwecken.
IntegerType
type IntegerType intIntegerType repräsentiert einen beliebigen Integer-Typ. Tatsächlich gehört dieser Typ nicht zum unsafe Paket, er erscheint hier nur zu Dokumentationszwecken.
Diese beiden Typen müssen nicht zu sehr beachtet werden. Sie sind nur Repräsentationen. Bei der Verwendung von Funktionen aus dem unsafe Paket wird der Editor Sie sogar darauf hinweisen, dass die Typen nicht übereinstimmen. Ihre tatsächlichen Typen sind die spezifischen Typen, die Sie übergeben.
Sizeof
func Sizeof(x ArbitraryType) uintptrGibt die Größe der Variable x in Bytes zurück, ohne die Größe ihres referenzierten Inhalts. Zum Beispiel:
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) uintptrDiese Funktion gibt den Offset eines Feldes innerhalb einer Struktur an. x muss also ein Strukturfeld sein. Der Rückgabewert ist die Anzahl der Bytes zwischen dem Anfang der Strukturadresse und dem Anfang der Feldadresse. Zum Beispiel:
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
Wenn Sie nicht wissen, was Speicherausrichtung bedeutet, können Sie hier nachlesen: Go Speicherausrichtung erklärt
func Alignof(x ArbitraryType) uintptrDie Ausrichtungsgröße ist normalerweise das Minimum aus der Maschinenwortlänge in Bytes und Sizeof. Auf einer amd64-Maschine beträgt die Wortlänge beispielsweise 64 Bit, also 8 Bytes. Zum Beispiel:
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 ist ein "Zeiger", der auf einen beliebigen Typ zeigen kann. Sein Typ ist *ArbitraryType. In Kombination mit uintptr entfaltet sich die wahre Leistungsfähigkeit des unsafe Pakets. In der offiziellen Dokumentation wird beschrieben, dass der unsafe.Pointer-Typ vier spezielle Operationen durchführen kann:
- Jeder Zeigertyp kann in
unsafe.Pointerkonvertiert werden unsafe.Pointerkann in jeden Zeigertyp konvertiert werdenuintptrkann inunsafe.Pointerkonvertiert werdenunsafe.Pointerkann inuintptrkonvertiert werden
Diese vier speziellen Operationen bilden das Fundament des gesamten unsafe Pakets. Erst durch diese Operationen ist es möglich, Code zu schreiben, der das Typsystem ignoriert und direkt Speicher liest und schreibt. Es wird empfohlen, bei der Verwendung besonders vorsichtig zu sein.
TIP
unsafe.Pointer kann nicht dereferenziert werden, ebenso kann nicht die Adresse davon genommen werden.
(1) *T1 in unsafe.Pointer und dann in *T2 konvertieren
Gegeben seien die Typen *T1 und *T2. Angenommen, T2 ist nicht größer als T1 und beide haben ein äquivalentes Speicherlayout, dann ist es erlaubt, Daten vom Typ T2 in T1 zu konvertieren. Zum Beispiel:
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.3Diese beiden Funktionen sind tatsächlich Teil des math-Pakets. Die Typänderungen im Prozess sind wie folgt:
float64 -> *float64 -> unsafe.Pointer -> *uint64 -> uint64 -> *uint64 -> unsafe.Pointer -> *float64 -> float64(2) unsafe.Pointer in uintptr konvertieren
Beim Konvertieren von unsafe.Pointer in uintptr wird die Adresse, auf die Ersteres zeigt, als Wert Letzterer. uintptr speichert die Adresse. Der Unterschied ist, dass Ersteres syntaktisch ein Zeiger ist, eine Referenz, während Letzteres nur ein Integer-Wert ist. Zum Beispiel:
func main() {
num := 1
fmt.Println(unsafe.Pointer(&num))
fmt.Printf("0x%x", uintptr(unsafe.Pointer(&num)))
}0xc00001c088
0xc00001c088Ein größerer Unterschied liegt in der Behandlung durch den Garbage Collector. Da unsafe.Pointer eine Referenz ist, wird sie bei Bedarf nicht vom Garbage Collector eingesammelt. Letzteres ist nur ein Wert und hat diese Sonderbehandlung natürlich nicht. Ein weiterer zu beachtender Punkt ist, dass wenn sich die Adresse des Elements ändert, auf das der Zeiger zeigt, der GC die alte Adresse der Zeigerreferenz aktualisiert, aber nicht den in uintptr gespeicherten Wert. Der folgende Code könnte beispielsweise Probleme verursachen:
func main() {
num := 16
address := uintptr(unsafe.Pointer(&num))
np := (*int64)(unsafe.Pointer(address))
fmt.Println(*np)
}In einigen Situationen, nachdem der GC die Variable verschoben hat, ist die Adresse, auf die address zeigt, bereits ungültig. Wenn dann versucht wird, einen Zeiger mit diesem Wert zu erstellen, wird eine panic ausgelöst:
panic: runtime error: invalid memory address or nil pointer dereferenceEs wird daher nicht empfohlen, den Wert von Pointer nach der Konvertierung in uintptr zu speichern.
(3) Durch uintptr in unsafe.Pointer konvertieren
Auf folgende Weise kann ein Zeiger durch uintptr erhalten werden. Solange der Zeiger gültig ist, tritt das Problem der ungültigen Adresse aus Beispiel 2 nicht auf. Pointer und Typzeiger selbst unterstützen keine Zeigerarithmetik, aber uintptr ist nur ein Integer-Wert, mit dem mathematische Operationen durchgeführt werden können. Nach der mathematischen Operation auf uintptr und der Konvertierung in Pointer kann Zeigerarithmetik durchgeführt werden.
p = unsafe.Pointer(uintptr(p) + offset)Auf diese Weise kann nur mit einem Zeiger auf interne Elemente einiger Typen zugegriffen werden, wie Arrays und Strukturen, unabhängig davon, ob ihre internen Elemente öffentlich sind. Zum Beispiel:
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 gibt ein mit dem Offset len aktualisiertes Pointer zurück, äquivalent zu Pointer(uintptr(ptr) + uintptr(len)):
Pointer(uintptr(ptr) + uintptr(len))Zum Beispiel:
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) *ArbitraryTypDiese Funktion empfängt ein Slice und gibt die Startadresse des zugrundeliegenden Arrays zurück. Ohne SliceData kann die Adresse des zugrundeliegenden Arrays nur durch die Adresse des ersten Elements ermittelt werden:
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)
}
}Natürlich kann auch durch den Typ reflect.SliceHeader darauf zugegriffen werden, aber in der Version 1.20 wurde er als veraltet markiert. SliceData wurde als Ersatz eingeführt. Ein Beispiel mit SliceData:
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) []ArbitraryTypeDie Slice-Funktion empfängt einen Zeiger und einen Längen-Offset. Sie gibt eine Slice-Repräsentation dieses Speicherbereichs zurück. Dabei findet keine Speicherkopie statt. Änderungen am Slice wirken sich direkt auf die Daten an dieser Adresse aus und umgekehrt. Sie wird normalerweise zusammen mit SliceData verwendet.
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]Das Ändern der Daten im numsRef1-Slice führt dazu, dass sich auch die Daten in nums ändern.
StringData
func StringData(str string) *byteÄhnlich wie die SliceData-Funktion, aber da die Konvertierung von Strings in Byte-Slices häufiger vorkommt, wurde sie separat aufgeführt. Ein Anwendungsbeispiel:
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))
}
}Da String-Literale im Nur-Lese-Segment des Prozesses gespeichert werden, wird das Programm sofort abstürzen und fatal melden, wenn Sie versuchen, die zugrundeliegenden Daten des Strings hier zu ändern. Für String-Variablen, die auf dem Heap oder Stack gespeichert sind, ist es jedoch zur Laufzeit vollständig möglich, ihre zugrundeliegenden Daten zu ändern.
String
func String(ptr *byte, len IntegerType) stringÄhnlich wie die Slice-Funktion. Sie empfängt einen Zeiger auf Byte-Typ und einen Längen-Offset und gibt dessen String-Repräsentation zurück. Dabei findet keine Speicherkopie statt. Hier ist ein Beispiel für die Konvertierung eines Byte-Slices in einen String:
func main() {
bytes := []byte("hello world")
str := unsafe.String(unsafe.SliceData(bytes), len(bytes))
fmt.Println(str)
}StringData und String führen bei der Konvertierung zwischen Strings und Byte-Slices keine Speicherkopie durch, was performanter ist als eine direkte Typkonvertierung. Sie sollten jedoch nur für Nur-Lese-Situationen verwendet werden. Wenn Sie planen, die Daten zu ändern, sollten Sie diese besser nicht verwenden.
