Що таке "жирний покажчик" у Rust?


91

Я вже читав термін "жирний покажчик" у декількох контекстах, але не знаю, що саме він означає і коли він використовується в Rust. Вказівник, здається, удвічі більший за звичайний, але я не розумію, чому. Здається, це також має щось спільне з об’єктами ознак.


7
Сам термін не є специфічним для іржі, до речі. Жирний покажчик, як правило, відноситься до покажчика, який зберігає деякі додаткові дані, крім просто адреси об’єкта, на який вказують. Якщо покажчик містить деякі біти тегів, і в залежності від цих бітів тегу, покажчик іноді взагалі не є покажчиком, він називається поданим тегом . (Наприклад, на багатьох віртуальних машинах Smalltalks, покажчики, які закінчуються 1 бітом, насправді є 31/63-бітними цілими числами, оскільки покажчики вирівняні за словом і, отже, ніколи не закінчуються на 1.) HotSpot JVM викликає свої жирові вказівники ООП (об'єктно-орієнтовані Покажчики).
Jörg W

1
Просто пропозиція: коли я публікую пару запитань та відповідей, я зазвичай пишу невеличку нотатку, в якій пояснюю, що це питання, на яке ми відповіли самостійно, і чому я вирішив його опублікувати. Погляньте на виноску у запитанні тут: stackoverflow.com/q/46147231/5768908
Gerardo Furtado

@GerardoFurtado Я спочатку розмістив тут коментар, в якому пояснював саме це. Але його було видалено зараз (не мною). Але так, я згоден, часто така нотатка корисна!
Лукас Кальбертодт,

Відповіді:


102

Термін "покажчик жиру" використовується для позначення посилань та необроблених покажчиків на типи динамічного розміру (DST) - фрагменти або об'єкти ознак. Покажчик жиру містить покажчик плюс деяку інформацію, яка робить літній час "повним" (наприклад, довжина).

Найчастіше використовувані типи в Rust не є літнім часом, але мають фіксований розмір, відомий під час компіляції. Ці типи реалізації на Sizedмежу . Навіть типи, які керують буфером купи динамічного розміру (наприклад Vec<T>), такі Sizedяк компілятор знає точну кількість байт, яку Vec<T>екземпляр займе у стеку. В даний час в Русті існує чотири різні типи літнього часу.


Фрагменти ( [T]і str)

Тип [T](для будь-якого T) має динамічний розмір (так само, як і спеціальний тип "зріз рядка" str). Ось чому ви зазвичай бачите це лише як &[T]або &mut [T], тобто за посиланням. Це посилання є так званим "жировим покажчиком". Давайте перевіримо:

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

Це друкує (з деяким очищенням):

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

Отже, ми бачимо, що посилання на звичайний тип, наприклад, u32має 8 байт, як і посилання на масив [u32; 2]. Ці два типи не є літнім часом. Але як [u32]і на літній час, посилання на нього вдвічі більше. У випадку зрізів додатковими даними, які "завершують" літній час, є просто довжина. Тож можна сказати, що представлення &[u32]приблизно такого:

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

Об'єкти ознак ( dyn Trait)

При використанні ознак як об'єктів ознак (тобто тип стирається, динамічно відправляється), ці об'єкти ознак є датами часу. Приклад:

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

Це друкує (з деяким очищенням):

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

Знову ж таки, &Catмає лише 8 байт, оскільки Catце нормальний тип. Але dyn Animalє об’єктом ознаки і тому має динамічний розмір. Таким чином, він &dyn Animalмає 16 байт.

У випадку об'єктів ознак додатковими даними, що заповнюють літній час, є вказівник на vtable (vptr). Я не можу повністю пояснити поняття vtables і vptrs тут, але вони використовуються для виклику правильної реалізації методу у цьому контексті віртуальної диспетчеризації. Vtable - це статична частина даних, яка в основному містить лише покажчик функції для кожного методу. При цьому посилання на об'єкт ознаки в основному представляється як:

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

(Це відрізняється від С ++, де vptr для абстрактних класів зберігається в об'єкті. Обидва підходи мають переваги та недоліки.)


Спеціальні переходи на літній час

Насправді можна створити власні датські часові пояси, маючи структуру, де останнє поле - літній час. Однак це досить рідко. Одним з яскравих прикладів є std::path::Path.

Посилання або вказівник на власний перехід на літній час також є показником жиру. Додаткові дані залежать від виду літнього часу в структурі.


Виняток: зовнішні типи

У RFC 1861 ця extern typeфункція була введена. Зовнішні типи - це також літній час, але вказівники на них не є показником жиру. Або точніше, як висловлюється RFC:

У Rust вказівники на DST містять метадані про об’єкт, на який вказують. Для рядків та фрагментів це довжина буфера, для об’єктів ознак це vtable об’єкта. Для зовнішніх типів метадані просто (). Це означає, що покажчик на тип extern має такий самий розмір, як і usize(тобто він не є "жировим покажчиком").

Але якщо ви не взаємодієте з інтерфейсом C, вам, мабуть, ніколи не доведеться мати справу з цими зовнішніми типами.




Вище ми бачили розміри незмінних посилань. Покажчики жиру працюють однаково для змінних посилань, незмінних необроблених покажчиків та змінних необроблених покажчиків:

size_of::<&[u32]>()       = 16
size_of::<&mut [u32]>()   = 16
size_of::<*const [u32]>() = 16
size_of::<*mut [u32]>()   = 16
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.