In Go belegt eine normale Struktur normalerweise einen Speicherblock. Es gibt jedoch einen Sonderfall: Wenn es sich um eine leere Struktur handelt, ist ihre Größe Null. Wie ist das möglich und welchen Nutzen hat eine leere Struktur?
Dieser Artikel wird zuerst im mittleren MPP-Plan veröffentlicht. Wenn Sie ein mittlerer Benutzer sind, folgen Sie mir bitte auf mittlerem Niveau. Vielen Dank.
type Test struct { A int B string } func main() { fmt.Println(unsafe.Sizeof(new(Test))) fmt.Println(unsafe.Sizeof(struct{}{})) } /* 8 0 */
Eine leere Struktur ist eine Struktur ohne Speichergröße. Diese Aussage ist richtig, aber genauer gesagt hat sie tatsächlich einen besonderen Ausgangspunkt: die Nullbasisvariable. Dies ist eine globale uintptr-Variable, die 8 Bytes belegt. Immer wenn unzählige struct {}-Variablen definiert werden, weist der Compiler die Adresse dieser Zerobase-Variablen zu. Mit anderen Worten: In Go verwendet jede Speicherzuweisung mit der Größe 0 dieselbe Adresse, &zerobase.
Beispiel
package main import "fmt" type emptyStruct struct {} func main() { a := struct{}{} b := struct{}{} c := emptyStruct{} fmt.Printf("%p\n", &a) fmt.Printf("%p\n", &b) fmt.Printf("%p\n", &c) } // 0x58e360 // 0x58e360 // 0x58e360
Die Speicheradressen der Variablen einer leeren Struktur sind alle gleich. Dies liegt daran, dass der Compiler während der Kompilierung &zerobase zuweist, wenn er auf diese spezielle Art der Speicherzuweisung stößt. Diese Logik befindet sich in der mallocgc-Funktion:
//go:linkname mallocgc func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { ... if size == 0 { return unsafe.Pointer(&zerobase) } ...
Das ist das Geheimnis der leeren Struktur. Mit dieser speziellen Variablen können wir viele Funktionalitäten realisieren.
Wenn eine leere Struktur Teil einer größeren Struktur ist, belegt sie normalerweise keinen Speicher. Es gibt jedoch einen Sonderfall, wenn die leere Struktur das letzte Feld ist; es löst eine Gedächtnisausrichtung aus.
Beispiel
type A struct { x int y string z struct{} } type B struct { x int z struct{} y string } func main() { println(unsafe.Alignof(A{})) println(unsafe.Alignof(B{})) println(unsafe.Sizeof(A{})) println(unsafe.Sizeof(B{})) } /** 8 8 32 24 **/
Wenn ein Zeiger auf ein Feld vorhanden ist, liegt die zurückgegebene Adresse möglicherweise außerhalb der Struktur, was möglicherweise zu Speicherverlusten führt, wenn der Speicher beim Freigeben der Struktur nicht freigegeben wird. Wenn eine leere Struktur das letzte Feld einer anderen Struktur ist, wird daher aus Sicherheitsgründen zusätzlicher Speicher zugewiesen. Befindet sich die leere Struktur am Anfang oder in der Mitte, ist ihre Adresse dieselbe wie die der nächsten Variablen.
type A struct { x int y string z struct{} } type B struct { x int z struct{} y string } func main() { a := A{} b := B{} fmt.Printf("%p\n", &a.y) fmt.Printf("%p\n", &a.z) fmt.Printf("%p\n", &b.y) fmt.Printf("%p\n", &b.z) } /** 0x1400012c008 0x1400012c018 0x1400012e008 0x1400012e008 **/
Der Hauptgrund für die Existenz der leeren Struktur struct{} besteht darin, Speicher zu sparen. Wenn Sie eine Struktur benötigen, sich aber nicht um deren Inhalt kümmern, sollten Sie die Verwendung einer leeren Struktur in Betracht ziehen. Die wichtigsten zusammengesetzten Strukturen von Go wie Map, Chan und Slice können alle struct{} verwenden.
// Create map m := make(map[int]struct{}) // Assign value m[1] = struct{}{} // Check if key exists _, ok := m[1]
Ein klassisches Szenario kombiniert Kanal und Struktur{}, wobei Struktur{} oft als Signal verwendet wird, ohne sich um seinen Inhalt zu kümmern. Wie in früheren Artikeln analysiert, besteht die wesentliche Datenstruktur eines Kanals aus einer Verwaltungsstruktur und einem Ringpuffer. Der Ringpuffer wird mit Null belegt, wenn struct{} als Element verwendet wird.
Die einzige gemeinsame Verwendung von chan und struct{} dient der Signalübertragung, da die leere Struktur selbst keinen Wert tragen kann. Im Allgemeinen wird es ohne Pufferkanäle verwendet.
// Create a signal channel waitc := make(chan struct{}) // ... goroutine 1: // Send signal: push element waitc <- struct{}{} // Send signal: close close(waitc) goroutine 2: select { // Receive signal and perform corresponding actions case <-waitc: }
Ist struct{} in diesem Szenario unbedingt erforderlich? Nicht wirklich, und der eingesparte Speicher ist vernachlässigbar. Der entscheidende Punkt ist, dass der Elementwert von chan nicht berücksichtigt wird, daher wird struct{} verwendet.
Das obige ist der detaillierte Inhalt vonGo entschlüsseln: leere Struktur. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!