Rust'ta "şişman işaretçi" nedir?


91

"Şişman işaretçi" terimini zaten çeşitli bağlamlarda okudum, ancak tam olarak ne anlama geldiğinden ve Rust'ta ne zaman kullanıldığından emin değilim. İşaretçi, normal bir işaretçiden iki kat daha büyük görünüyor, ancak nedenini anlamıyorum. Aynı zamanda özel nesnelerle bir ilgisi var gibi görünüyor.


7
Terimin kendisi Rust'a özgü değildir, BTW. Şişman işaretçi genellikle sadece işaret edilen nesnenin adresinin yanı sıra bazı ekstra verileri depolayan bir işaretçiyi ifade eder. İşaretçi bazı etiket bitleri içeriyorsa ve bu etiket bitlerine bağlı olarak, işaretçi bazen hiç bir işaretçi değildir, buna etiketli işaretçi temsili denir . (Örneğin, birçok Smalltalks sanal makinesinde, 1 bit ile biten işaretçiler aslında 31/63-bit tam sayılardır, çünkü işaretçiler sözcük hizalıdır ve bu nedenle asla 1'de bitmez.) HotSpot JVM, şişman işaretçilerinin OOP'larını (Nesne Yönelimli İşaretçiler).
Jörg W Mittag

1
Sadece bir öneri: Bir Soru-Cevap çifti yayınladığımda normalde bunun kendi kendine cevaplanan bir soru olduğunu ve neden onu göndermeye karar verdiğimi açıklayan küçük bir not yazarım. Burada sorudaki dipnota bir göz atın: stackoverflow.com/q/46147231/5768908
Gerardo Furtado

@GerardoFurtado Başlangıçta burada tam olarak bunu açıklayan bir yorum yayınladım. Ama şimdi kaldırıldı (benim tarafımdan değil). Ama evet, katılıyorum, genellikle böyle bir not yararlıdır!
Lukas Kalbertodt

Yanıtlar:


102

"Yağ işaretçisi" terimi, dinamik olarak boyutlandırılmış türlere (DST'ler) - dilimler veya özellik nesneleri - referansları ve ham işaretçileri ifade etmek için kullanılır . Bir şişman işaretçi, bir işaretçi artı DST'yi "tamamlayan" (örneğin uzunluk) bazı bilgiler içerir.

Rust'ta en yaygın olarak kullanılan türler DST değildir , ancak derleme zamanında bilinen sabit bir boyuta sahiptir. Bu tipler uygulamak özelliği . Dinamik boyuttaki bir yığın arabelleğini (gibi ) yöneten türler bile , derleyicinin bir örneğin yığın üzerinde alacağı tam bayt sayısını bildiği gibidir . Rust'ta şu anda dört farklı DST türü vardır.SizedVec<T>SizedVec<T>


Dilimler ( [T]ve str)

Tür [T](herhangi Tbiri için ) dinamik olarak boyutlandırılmıştır (özel "dize dilimi" türü de öyledir str). Bu yüzden onu genellikle sadece &[T]veya &mut [T]yani bir referansın arkasında görüyorsunuz . Bu referans, sözde "şişman gösterici" dir. Hadi kontrol edelim:

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

Bu (biraz temizleme ile) yazdırır:

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

Böylece u32, bir diziye referans olduğu gibi normal bir türe yapılan bir referansın 8 bayt büyüklüğünde olduğunu görüyoruz [u32; 2]. Bu iki tür DST değildir. Ancak [u32]DST olduğu gibi , ona yapılan referans iki kat daha büyüktür. Dilimler söz konusu olduğunda, DST'yi "tamamlayan" ek veriler yalnızca uzunluktur. Dolayısıyla temsili &[u32]şunun gibi bir şey diyebiliriz :

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

Özellik nesneleri ( dyn Trait)

Nitelikleri özellik nesneleri olarak kullanırken (yani silinen, dinamik olarak gönderilen türler), bu özellik nesneleri DST'lerdir. Misal:

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

Bu (biraz temizleme ile) yazdırır:

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

Yine, &Catsadece 8 bayt büyüklüğündedir çünkü Catnormal bir türdür. Ancak dyn Animalözel bir nesnedir ve bu nedenle dinamik olarak boyutlandırılır. Bu nedenle &dyn Animal16 bayt büyüklüğündedir.

Özellik nesneleri durumunda, DST'yi tamamlayan ek veriler, vtable'a (vptr) bir göstericidir. Burada vtables ve vptr kavramlarını tam olarak açıklayamam, ancak bunlar bu sanal gönderim bağlamında doğru yöntem uygulamasını çağırmak için kullanılır. Vtable, temelde her yöntem için yalnızca bir işlev işaretçisi içeren statik bir veri parçasıdır. Bununla birlikte, bir özellik nesnesine yapılan referans temelde şu şekilde temsil edilir:

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

(Bu, soyut sınıflar için vptr'nin nesne içinde depolandığı C ++ 'dan farklıdır. Her iki yaklaşımın da avantajları ve dezavantajları vardır.)


Özel DST'ler

Son alanın DST olduğu bir yapıya sahip olarak kendi DST'lerinizi oluşturmak aslında mümkündür. Yine de bu oldukça nadirdir. Öne çıkan bir örnek std::path::Path.

Özel DST'ye bir referans veya işaretçi de bir şişman işaretçidir. Ek veriler, yapı içindeki DST türüne bağlıdır.


İstisna: İstisna türleri

In RFC 1861 , extern typeözelliği getirildi. Harici türler de DST'lerdir, ancak onlara yönelik işaretler şişman işaretçiler değildir . Veya daha doğrusu, RFC'nin dediği gibi:

Rust'ta, DST'lerin işaretçileri, işaret edilen nesne hakkındaki meta verileri taşır. Dizeler ve dilimler için bu, tamponun uzunluğudur, özellik nesneleri için bu, nesnenin vtable'sidir. Harici türleri için meta veriler basittir (). Bu, bir harici tipin göstericisinin a ile aynı boyuta sahip olduğu anlamına gelir usize(yani, "şişman işaretçi" değildir).

Ancak bir C arayüzü ile etkileşim kurmuyorsanız, muhtemelen bu harici tiplerle uğraşmak zorunda kalmayacaksınız.




Yukarıda, değişmez referanslar için boyutları gördük. Şişman işaretçiler, değiştirilebilir referanslar, değişmez ham işaretçiler ve değiştirilebilir ham işaretçiler için aynı şekilde çalışır:

size_of::<&[u32]>()       = 16
size_of::<&mut [u32]>()   = 16
size_of::<*const [u32]>() = 16
size_of::<*mut [u32]>()   = 16
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.