"Con trỏ béo" trong Rust là gì?


91

Tôi đã đọc thuật ngữ "con trỏ béo" trong nhiều ngữ cảnh rồi, nhưng tôi không chắc nó có nghĩa chính xác là gì và khi nào nó được sử dụng trong Rust. Con trỏ có vẻ lớn gấp đôi con trỏ bình thường, nhưng tôi không hiểu tại sao. Nó dường như cũng có liên quan đến các đối tượng đặc điểm.


7
Bản thân thuật ngữ này không dành riêng cho Rust, BTW. Fat pointer thường đề cập đến một con trỏ lưu trữ một số dữ liệu bổ sung ngoài địa chỉ của đối tượng được trỏ tới. Nếu con trỏ chứa một số bit thẻ và tùy thuộc vào các bit thẻ đó, con trỏ đôi khi không phải là một con trỏ, nó được gọi là biểu diễn con trỏ được gắn thẻ . (Ví dụ: trên nhiều máy ảo Smalltalks, các con trỏ kết thúc bằng 1 bit thực sự là số nguyên 31/63 bit, vì các con trỏ được căn chỉnh từ và do đó không bao giờ kết thúc bằng 1.) HotSpot JVM gọi các con trỏ béo của nó là OOP s (Hướng đối tượng Con trỏ).
Jörg W Mittag

1
Chỉ là một gợi ý: khi tôi đăng một cặp Hỏi & Đáp, tôi thường viết một ghi chú nhỏ giải thích rằng đó là một câu hỏi tự trả lời và tại sao tôi quyết định đăng nó. Hãy nhìn vào chú thích trong câu hỏi ở đây: stackoverflow.com/q/46147231/5768908
Gerardo Furtado

@GerardoFurtado Ban đầu tôi đã đăng một nhận xét ở đây giải thích chính xác điều đó. Nhưng nó đã được gỡ bỏ bây giờ (không phải bởi tôi). Nhưng vâng, tôi đồng ý, thường thì một ghi chú như vậy rất hữu ích!
Lukas Kalbertodt

Câu trả lời:


102

Thuật ngữ "con trỏ béo" được sử dụng để chỉ các tham chiếu và con trỏ thô đến các loại có kích thước động (DST) - các lát cắt hoặc các đối tượng đặc điểm. Một con trỏ béo chứa một con trỏ cộng với một số thông tin làm cho DST "hoàn chỉnh" (ví dụ: độ dài).

Hầu hết các loại được sử dụng phổ biến trong Rust không phải là DST, nhưng có kích thước cố định đã biết tại thời điểm biên dịch. Những loại thực hiện các Sizedđặc điểm . Ngay cả các kiểu quản lý bộ đệm heap có kích thước động (như Vec<T>) giống Sizednhư trình biên dịch biết số byte chính xác mà một Vec<T>cá thể sẽ chiếm trên ngăn xếp. Hiện có bốn loại DST khác nhau trong Rust.


Slices ( [T]str)

Kiểu [T](cho bất kỳ T) có kích thước động (kiểu "lát chuỗi" đặc biệt cũng vậy str). Đó là lý do tại sao bạn thường chỉ thấy nó là &[T]hoặc &mut [T], tức là đằng sau một tham chiếu. Tham chiếu này được gọi là "con trỏ béo". Hãy kiểm tra:

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

Bản in này (với một số lần dọn dẹp):

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

Vì vậy, chúng ta thấy rằng một tham chiếu đến một kiểu bình thường như u32là 8 byte lớn, cũng như một tham chiếu đến một mảng [u32; 2]. Hai loại đó không phải là DST. Nhưng cũng như [u32]DST, tham chiếu đến nó lớn gấp đôi. Trong trường hợp các lát, dữ liệu bổ sung "hoàn thành" DST chỉ đơn giản là độ dài. Vì vậy, người ta có thể nói đại diện của &[u32]là một cái gì đó như thế này:

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

Đối tượng đặc điểm ( dyn Trait)

Khi sử dụng các đặc điểm làm đối tượng đặc điểm (tức là loại bị xóa, di chuyển động), các đối tượng đặc điểm này là DST. Thí dụ:

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

Bản in này (với một số lần dọn dẹp):

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

Một lần nữa, &Catchỉ lớn 8 byte vì Catlà kiểu bình thường. Nhưng dyn Animallà một đối tượng đặc điểm và do đó có kích thước động. Như vậy, &dyn Animallà 16 byte lớn.

Trong trường hợp các đối tượng đặc điểm, dữ liệu bổ sung để hoàn thành DST là một con trỏ tới vtable (vptr). Tôi không thể giải thích đầy đủ khái niệm vtables và vptrs ở đây, nhưng chúng được sử dụng để gọi việc triển khai phương thức chính xác trong ngữ cảnh điều phối ảo này. Vtable là một phần dữ liệu tĩnh về cơ bản chỉ chứa một con trỏ hàm cho mỗi phương thức. Với điều đó, một tham chiếu đến một đối tượng đặc điểm về cơ bản được biểu diễn dưới dạng:

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

(Điều này khác với C ++, trong đó vptr cho các lớp trừu tượng được lưu trữ bên trong đối tượng. Cả hai cách tiếp cận đều có ưu điểm và nhược điểm.)


DST tùy chỉnh

Thực sự có thể tạo DST của riêng bạn bằng cách có một cấu trúc trong đó trường cuối cùng là DST. Tuy nhiên, điều này là khá hiếm. Một ví dụ nổi bật là std::path::Path.

Tham chiếu hoặc con trỏ đến DST tùy chỉnh cũng là một con trỏ béo. Dữ liệu bổ sung phụ thuộc vào loại DST bên trong cấu trúc.


Ngoại lệ: Các loại ngoại lệ

Trong RFC 1861 , extern typetính năng này đã được giới thiệu. Các kiểu extern cũng là DST, nhưng con trỏ tới chúng không phải là con trỏ béo. Hay chính xác hơn, như RFC đặt nó:

Trong Rust, con trỏ đến DST mang siêu dữ liệu về đối tượng được trỏ tới. Đối với các chuỗi và các lát, đây là độ dài của bộ đệm, đối với các đối tượng đặc điểm, đây là vtable của đối tượng. Đối với các loại extern, siêu dữ liệu chỉ đơn giản (). Điều này có nghĩa là một con trỏ tới kiểu extern có cùng kích thước với a usize(tức là nó không phải là "con trỏ béo").

Nhưng nếu bạn không tương tác với giao diện C, bạn có thể sẽ không bao giờ phải đối mặt với những kiểu ngoại lệ này.




Ở trên, chúng ta đã thấy các kích thước cho các tham chiếu bất biến. Con trỏ béo hoạt động giống nhau đối với các tham chiếu có thể thay đổi, con trỏ thô bất biến và con trỏ thô có thể thay đổi:

size_of::<&[u32]>()       = 16
size_of::<&mut [u32]>()   = 16
size_of::<*const [u32]>() = 16
size_of::<*mut [u32]>()   = 16
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.