Co to jest „gruby wskaźnik” w Rust?

91

Czytałem już termin „gruby wskaźnik” w kilku kontekstach, ale nie jestem pewien, co dokładnie oznacza i kiedy jest używany w Rust. Wskaźnik wydaje się być dwa razy większy niż normalny wskaźnik, ale nie rozumiem dlaczego. Wydaje się również, że ma to coś wspólnego z obiektami cech.

Lukas Kalbertodt
źródło
7
Tak przy okazji, sam termin nie jest specyficzny dla Rdzy. Fat wskaźnik ogólnie odnosi się do wskaźnika, który przechowuje dodatkowe dane poza tylko adresem wskazywanego obiektu. Jeśli wskaźnik zawiera kilka bitów znacznika iw zależności od tych bitów znacznika, wskaźnik czasami w ogóle nie jest wskaźnikiem, nazywa się to oznaczoną reprezentacją wskaźnika . (Np. W wielu maszynach wirtualnych Smalltalks wskaźniki kończące się 1 bitem są w rzeczywistości 31/63-bitowymi liczbami całkowitymi, ponieważ wskaźniki są wyrównane do słów i nigdy nie kończą się na 1.) HotSpot JVM wywołuje swoje grube wskaźniki OOP s (Object-Oriented Wskaźniki).
Jörg W Mittag,
1
Tylko sugestia: kiedy publikuję parę pytań i odpowiedzi, zwykle piszę krótką notatkę wyjaśniającą, że jest to pytanie, na które odpowiedziano samodzielnie i dlaczego zdecydowałem się je opublikować. Spójrz na przypis w pytaniu tutaj: stackoverflow.com/q/46147231/5768908
Gerardo Furtado
@GerardoFurtado Na początku zamieściłem tutaj komentarz wyjaśniający dokładnie to. Ale teraz został usunięty (nie przeze mnie). Ale tak, zgadzam się, często taka notatka się przydaje!
Lukas Kalbertodt
@ Jörg W Mittag
``

Odpowiedzi:

102

Termin „gruby wskaźnik” jest używany w odniesieniu do odniesień i surowych wskaźników do typów o dynamicznej wielkości (DST) - plasterków lub obiektów cech. Gruby wskaźnik zawiera wskaźnik plus pewne informacje, które sprawiają, że DST jest „kompletny” (np. Długość).

Najczęściej używanymi typami w Rust nie są DST, ale mają one stały rozmiar znany w czasie kompilacji. Te typy wdrożyć ten Sizedcechę . Nawet typy, które zarządzają buforem sterty o rozmiarze dynamicznym (takim jak Vec<T>), są Sizedtakie, jakie kompilator zna dokładną liczbę bajtów, które Vec<T>instancja zajmie na stosie. Obecnie w Rust istnieją cztery różne rodzaje czasu letniego.


Plasterki ( [T]i str)

Typ [T](dla dowolnego T) jest określany dynamicznie (tak samo jak specjalny typ „wycinka łańcucha” str). Dlatego zwykle widzisz to tylko jako &[T]lub &mut [T], tj. Za odniesieniem. To odniesienie jest tak zwanym „wskaźnikiem tłuszczu”. Sprawdźmy:

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

To drukuje (z pewnym porządkiem):

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

Widzimy więc, że odniesienie do normalnego typu, takiego jak, u32ma 8 bajtów, podobnie jak odniesienie do tablicy [u32; 2]. Te dwa typy nie są DST. Ale tak jak w [u32]przypadku czasu letniego, odniesienie do niego jest dwukrotnie większe. W przypadku wycinków dodatkowe dane, które „uzupełniają” czas letni, to po prostu długość. Można więc powiedzieć, że reprezentacja &[u32]wygląda mniej więcej tak:

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

Obiekty cech ( dyn Trait)

W przypadku używania cech jako obiektów cech (tj. Wymazywania typu, dynamicznego wysyłania), te obiekty cech są DST. Przykład:

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>());

To drukuje (z pewnym porządkiem):

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

Ponownie, &Catma tylko 8 bajtów, ponieważ Catjest typem normalnym. Ale dyn Animaljest obiektem cech, a zatem ma dynamiczne rozmiary. W związku z tym &dyn Animalma rozmiar 16 bajtów.

W przypadku obiektów cech, dodatkowymi danymi, które uzupełniają DST, jest wskaźnik do tabeli vtable (vptr). Nie mogę w pełni wyjaśnić tutaj koncepcji vtables i vptrs, ale są one używane do wywoływania poprawnej implementacji metody w tym kontekście wirtualnej wysyłki. Tabela vtable to statyczny fragment danych, który w zasadzie zawiera tylko wskaźnik funkcji dla każdej metody. W związku z tym odniesienie do obiektu cechy jest zasadniczo reprezentowane jako:

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

(Różni się to od C ++, gdzie vptr dla klas abstrakcyjnych jest przechowywany w obiekcie. Oba podejścia mają zalety i wady).


Niestandardowe DST

W rzeczywistości możliwe jest utworzenie własnego czasu letniego, mając strukturę, w której ostatnim polem jest czas letni. Jest to jednak raczej rzadkie. Jednym z wybitnych przykładów jest std::path::Path.

Odniesienie lub wskaźnik do niestandardowego czasu letniego jest również grubym wskaźnikiem. Dodatkowe dane zależą od rodzaju czasu letniego wewnątrz struktury.


Wyjątek: typy zewnętrzne

W dokumencie RFC 1861 , extern typefunkcja została wprowadzona. Typy zewnętrzne to także DST, ale wskaźniki do nich nie są grubymi wskaźnikami. A dokładniej, jak to ujmuje RFC:

W Rust wskaźniki do DST zawierają metadane dotyczące wskazywanego obiektu. Dla łańcuchów i plasterków jest to długość bufora, dla obiektów cech jest to vtable obiektu. W przypadku typów zewnętrznych metadane są po prostu (). Oznacza to, że wskaźnik do typu zewnętrznego ma taki sam rozmiar jak a usize(tj. Nie jest „grubym wskaźnikiem”).

Ale jeśli nie korzystasz z interfejsu C, prawdopodobnie nigdy nie będziesz musiał radzić sobie z tymi zewnętrznymi typami.




Powyżej widzieliśmy rozmiary niezmiennych odwołań. Grubsze wskaźniki działają tak samo dla zmiennych odwołań, niezmiennych wskaźników surowych i zmiennych wskaźników surowych:

size_of::<&[u32]>()       = 16
size_of::<&mut [u32]>()   = 16
size_of::<*const [u32]>() = 16
size_of::<*mut [u32]>()   = 16
Lukas Kalbertodt
źródło