“ ตัวชี้ไขมัน” คืออะไร?


95

ฉันได้อ่านคำว่า "fat pointer" ในหลาย ๆ บริบทแล้ว แต่ฉันไม่แน่ใจว่ามันหมายถึงอะไรและเมื่อใดที่ใช้ใน Rust ตัวชี้ดูเหมือนจะใหญ่กว่าตัวชี้ปกติถึงสองเท่า แต่ฉันไม่เข้าใจว่าทำไม ดูเหมือนว่าจะมีบางอย่างเกี่ยวข้องกับวัตถุลักษณะ


7
คำนี้ไม่ได้เป็นสนิมเฉพาะ BTW โดยทั่วไปตัวชี้ไขมันหมายถึงตัวชี้ที่เก็บข้อมูลพิเศษบางอย่างนอกเหนือจากที่อยู่ของวัตถุที่ชี้ไป หากตัวชี้มีบางส่วนบิตแท็กและขึ้นอยู่กับผู้ที่บิตแท็กตัวชี้บางครั้งไม่ได้เป็นตัวชี้ที่ทุกคนจะเรียกว่าเป็นตัวแทนชี้แท็ก (เช่นใน Smalltalks VM หลายตัวพอยน์เตอร์ที่ลงท้ายด้วย 1 บิตเป็นจำนวนเต็ม 31/63 บิตเนื่องจากพอยน์เตอร์จัดแนวคำจึงไม่สิ้นสุดใน 1) HotSpot JVM เรียกตัวชี้ไขมันOOP s (Object-Oriented พอยน์เตอร์).
Jörg W Mittag

2
เป็นเพียงข้อเสนอแนะ: เมื่อฉันโพสต์คำถามและคำตอบคู่ปกติฉันจะเขียนบันทึกเล็ก ๆ เพื่ออธิบายว่าเป็นคำถามที่ตอบได้เองและทำไมฉันถึงตัดสินใจโพสต์ ดูเชิงอรรถในคำถามได้ที่นี่: stackoverflow.com/q/46147231/5768908
Gerardo Furtado

@GerardoFurtado ตอนแรกฉันโพสต์ความคิดเห็นที่นี่อธิบายว่า แต่ตอนนี้มันถูกลบออกไปแล้ว (ไม่ใช่โดยฉัน) แต่ใช่ฉันเห็นด้วยบ่อยครั้งโน้ตดังกล่าวมีประโยชน์!
Lukas Kalbertodt

คำตอบ:


110

คำว่า "ตัวชี้ไขมัน" ใช้เพื่ออ้างถึงการอ้างอิงและตัวชี้ดิบไปยังประเภทขนาดแบบไดนามิก (DST) - ชิ้นส่วนหรือวัตถุลักษณะ ตัวชี้ไขมันประกอบด้วยตัวชี้พร้อมข้อมูลบางอย่างที่ทำให้ DST "สมบูรณ์" (เช่นความยาว)

ประเภทที่ใช้บ่อยที่สุดใน Rust ไม่ใช่ DST แต่มีขนาดคงที่ซึ่งทราบเวลาคอมไพล์ ประเภทนี้ใช้ลักษณะ แม้แต่ประเภทที่จัดการฮีปบัฟเฟอร์ที่มีขนาดไดนามิก (เช่น) ก็เป็นได้เนื่องจากคอมไพเลอร์รู้จำนวนไบต์ที่แน่นอนที่อินสแตนซ์จะใช้ในสแต็ก ปัจจุบันมี DST สี่ประเภทที่แตกต่างกันใน RustSizedVec<T>SizedVec<T>


ชิ้น ( [T]และstr)

ประเภท[T](สำหรับรายการใด ๆT) มีขนาดแบบไดนามิก (ดังนั้นประเภท "string slice" พิเศษ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]ไบต์มีขนาดใหญ่ที่สุดเท่าที่มีการอ้างอิงไปยังอาร์เรย์ ทั้งสองประเภทไม่ใช่ DST แต่เช่นเดียว[u32]กับ DST การอ้างอิงถึงมีขนาดใหญ่กว่าสองเท่า ในกรณีของชิ้นส่วนข้อมูลเพิ่มเติมที่ "เสร็จสมบูรณ์" DST เป็นเพียงความยาว ดังนั้นใคร ๆ ก็บอกได้ว่าการเป็นตัวแทน&[u32]คืออะไรแบบนี้:

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

ลักษณะวัตถุ ( dyn Trait)

เมื่อใช้ลักษณะเป็นวัตถุลักษณะ (เช่นประเภทลบออกส่งแบบไดนามิก) วัตถุลักษณะเหล่านี้คือ DST ตัวอย่าง:

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 ไบต์

ในกรณีของ trait object ข้อมูลเพิ่มเติมที่ทำให้ DST สมบูรณ์คือตัวชี้ไปยัง vtable (vptr) ฉันไม่สามารถอธิบายแนวคิดของ vtables และ vptrs ได้อย่างสมบูรณ์ที่นี่ แต่ใช้เพื่อเรียกใช้วิธีการที่ถูกต้องในบริบทการจัดส่งเสมือนนี้ vtable เป็นส่วนข้อมูลคงที่ซึ่งโดยพื้นฐานแล้วจะมีตัวชี้ฟังก์ชันสำหรับแต่ละวิธีเท่านั้น ด้วยเหตุนี้การอ้างอิงถึงวัตถุลักษณะโดยทั่วไปจะแสดงเป็น:

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

(ซึ่งแตกต่างจาก C ++ ที่ vptr สำหรับคลาสนามธรรมจะถูกเก็บไว้ภายในออบเจ็กต์ทั้งสองวิธีมีข้อดีและข้อเสีย)


DST ที่กำหนดเอง

เป็นไปได้จริงที่จะสร้าง DST ของคุณเองโดยมีโครงสร้างที่ฟิลด์สุดท้ายเป็น DST สิ่งนี้ค่อนข้างหายาก std::path::Pathตัวอย่างหนึ่งที่โดดเด่นคือ

การอ้างอิงหรือตัวชี้ไปยัง DST ที่กำหนดเองยังเป็นตัวชี้ไขมัน ข้อมูลเพิ่มเติมขึ้นอยู่กับชนิดของ DST ภายในโครงสร้าง


ข้อยกเว้น: ประเภท Extern

ในRFC 1861ที่extern typeคุณสมบัติได้รับการแนะนำ ประเภท Extern ก็เป็น DST เช่นกัน แต่คำแนะนำไม่ใช่ตัวชี้ไขมัน หรือมากกว่านั้นตามที่ RFC วางไว้:

ใน Rust ตัวชี้ไปยัง DST จะมีข้อมูลเมตาเกี่ยวกับวัตถุที่ชี้ไป สำหรับสตริงและชิ้นส่วนนี่คือความยาวของบัฟเฟอร์สำหรับ trait object นี่คือ vtable ของอ็อบเจ็กต์ ประเภท extern ()เมตาดาต้าเป็นเพียง ซึ่งหมายความว่าตัวชี้ไปยังชนิดภายนอกมีขนาดเท่ากับ a usize(กล่าวคือไม่ใช่ "ตัวชี้ไขมัน")

แต่ถ้าคุณไม่ได้โต้ตอบกับอินเทอร์เฟซ C คุณอาจไม่ต้องจัดการกับประเภทภายนอกเหล่านี้




ด้านบนเราได้เห็นขนาดสำหรับการอ้างอิงที่ไม่เปลี่ยนรูป ตัวชี้ไขมันทำงานเหมือนกันสำหรับการอ้างอิงที่ไม่แน่นอนตัวชี้ดิบที่ไม่เปลี่ยนรูปและตัวชี้ดิบที่ไม่แน่นอน:

size_of::<&[u32]>()       = 16
size_of::<&mut [u32]>()   = 16
size_of::<*const [u32]>() = 16
size_of::<*mut [u32]>()   = 16
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.