pointers - Was ist ein „fetter Zeiger“ in Rust?



(1)

Ich habe den Begriff "fetter Zeiger" bereits in mehreren Zusammenhängen gelesen, bin mir aber nicht sicher, was er genau bedeutet und wann er in Rust verwendet wird. Der Zeiger scheint doppelt so groß zu sein wie ein normaler Zeiger, aber ich verstehe nicht warum. Es scheint auch etwas mit Merkmalsobjekten zu tun zu haben.


Der Begriff "Fettzeiger" bezieht sich auf Verweise und Rohzeiger auf dynamisch dimensionierte Typen (DSTs) - Schichten oder Merkmalsobjekte. Ein fetter Zeiger enthält einen Zeiger sowie einige Informationen, die die Sommerzeit "vervollständigen" (z. B. die Länge).

Die in Rust am häufigsten verwendeten Typen sind keine DSTs, sondern haben eine feste Größe, die zum Zeitpunkt der Kompilierung bekannt ist. Diese Typen implementieren das Merkmal "Größe" . Sogar Typen, die einen Heap-Puffer mit dynamischer Größe verwalten (wie Vec<T> ), haben die Größe, da der Compiler die genaue Anzahl von Bytes kennt, die eine Vec<T> -Instanz auf dem Stack einnimmt. Derzeit gibt es in Rust vier verschiedene Arten von Sommerzeit.


Scheiben ( [T] und str )

Der Typ [T] (für jedes T ) hat eine dynamische Größe (ebenso der spezielle "String Slice" -Typ str ). Deshalb sehen Sie es normalerweise nur als &[T] oder &mut [T] , dh hinter einer Referenz. Diese Referenz ist ein sogenannter "Fettzeiger". Lass uns das Prüfen:

dbg!(size_of::<&u32>());
dbg!(size_of::<&[u32; 2]>());
dbg!(size_of::<&[u32]>());

Dies druckt (mit einigen Aufräumarbeiten):

size_of::<&u32>()      = 8
size_of::<&[u32; 2]>() = 8
size_of::<&[u32]>()    = 16

Wir sehen also, dass ein Verweis auf einen normalen Typ wie u32 8 Bytes groß ist, ebenso wie ein Verweis auf ein Array [u32; 2] [u32; 2] . Diese beiden Typen sind keine Sommerzeit. Da jedoch [u32] eine Sommerzeit ist, ist der Verweis darauf doppelt so groß. Im Fall von Schichten sind die zusätzlichen Daten, die die Sommerzeit "vervollständigen", einfach die Länge. Man könnte also sagen, die Darstellung von &[u32] ist &[u32] so:

struct SliceRef { 
    ptr: *const u32, 
    len: usize,
}


Merkmalsobjekte ( dyn Trait )

Bei der Verwendung von Merkmalen als Merkmalobjekte (dh Typ gelöscht, dynamisch verteilt) handelt es sich bei diesen Merkmalobjekten um DSTs. Beispiel:

trait Animal {
    fn speak(&self);
}

struct Cat;
impl Animal for Cat {
    fn speak(&self) {
        println!("meow");
    }
}

dbg!(size_of::<&Cat>());
dbg!(size_of::<&dyn Animal>());

Dies druckt (mit einigen Aufräumarbeiten):

size_of::<&Cat>()        = 8
size_of::<&dyn Animal>() = 16

&Cat ist wiederum nur 8 Byte groß, da Cat ein normaler Typ ist. Aber dyn Animal ist ein Eigenschaftsobjekt und daher dynamisch dimensioniert. Als solches ist &dyn Animal 16 Bytes groß.

Bei Merkmalobjekten sind die zusätzlichen Daten, die die Sommerzeit vervollständigen, ein Zeiger auf die vtable (den vptr). Ich kann das Konzept von vtables und vptrs hier nicht vollständig erläutern, aber sie werden verwendet, um die korrekte Methodenimplementierung in diesem virtuellen Dispatch-Kontext aufzurufen. Die vtable ist ein statisches Datenelement, das grundsätzlich nur einen Funktionszeiger für jede Methode enthält. Ein Verweis auf ein Merkmalsobjekt wird dabei grundsätzlich wie folgt dargestellt:

struct TraitObjectRef {
    data_ptr: *const (),
    vptr: *const (),
}

(Dies unterscheidet sich von C ++, wo die vptr für abstrakte Klassen im Objekt gespeichert ist. Beide Ansätze haben Vor- und Nachteile.)


Benutzerdefinierte Sommerzeit

Es ist tatsächlich möglich, Ihre eigenen Sommerzeiten zu erstellen, indem Sie eine Struktur haben, in der das letzte Feld eine Sommerzeit ist. Dies ist jedoch eher selten. Ein prominentes Beispiel ist std::path::Path .

Ein Verweis oder Zeiger auf die benutzerdefinierte Sommerzeit ist auch ein Fettzeiger. Die zusätzlichen Daten hängen von der Art der Sommerzeit in der Struktur ab.


Ausnahme: Externe Typen

In RFC 1861 wurde die Funktion für extern type eingeführt. Externe Typen sind ebenfalls DSTs, aber Zeiger auf diese Typen sind keine Fettzeiger. Oder genauer gesagt, wie der RFC es ausdrückt:

In Rust enthalten Zeiger auf DSTs Metadaten zu dem Objekt, auf das verwiesen wird. Bei Strings und Slices ist dies die Länge des Puffers, bei Trait-Objekten die vtable des Objekts. Für externe Typen sind die Metadaten einfach () . Dies bedeutet, dass ein Zeiger auf einen externen Typ dieselbe Größe hat wie ein usize (dh es ist kein "fetter Zeiger").

Wenn Sie jedoch nicht mit einer C-Schnittstelle interagieren, müssen Sie sich wahrscheinlich nie mit diesen externen Typen befassen.



Oben haben wir die Größen für unveränderliche Referenzen gesehen. Fettzeiger funktionieren genauso für veränderbare Referenzen, unveränderbare Rohzeiger und veränderbare Rohzeiger:

size_of::<&[u32]>()       = 16
size_of::<&mut [u32]>()   = 16
size_of::<*const [u32]>() = 16
size_of::<*mut [u32]>()   = 16




rust