在下面的 Rust 代码中,aBox
或Rc
包含 a 的u32
是 8 字节,而 aBox
或Rc
包含一个切片的 是 16 字节。我理解这是为什么;指向动态大小内存区域(例如切片)的智能指针除了地址之外,还会包含该内存区域的长度。
fn main()
{
println!("Size of Box<u32>: {} bytes", std::mem::size_of::<Box<u32>>()); // 8
println!("Size of Box<[u32]>: {} bytes", std::mem::size_of::<Box<[u32]>>()); // 16
println!("Size of Box<str>: {} bytes", std::mem::size_of::<Box<str>>()); // 16
println!("Size of Rc<u32>: {} bytes", std::mem::size_of::<Rc<u32>>()); // 8
println!("Size of Rc<[u32]>: {} bytes", std::mem::size_of::<Rc<[u32]>>()); // 16
println!("Size of Rc<str>: {} bytes", std::mem::size_of::<Rc<str>>()); // 16
}
我的问题是 Rust 的编译器如何知道指针需要多大以及如何实际使用或解释额外的长度。
首先,以下是我对指针大小的最佳猜测。 和 都Rc
具有Box
以下结构:
pub struct Box<T: ?Sized, A: Allocator = Global>(Unique<T>, A);
pub struct Rc<T: ?Sized, A: Allocator = Global> {
ptr: NonNull<RcInner<T>>,
phantom: PhantomData<RcInner<T>>,
alloc: A,
}
我假设该类型以某种方式为/Allocator
提供了额外的 8 个字节空间作为长度,但我不知道这实际上是怎么发生的。我查看了分配器代码,但不明白在这里包含分配器实际上起了什么作用。如果我要创建自己的指针/容器类型,那么在我的结构体中包含该类型是否足以实现这种行为?Box
Rc
Allocator
至于长度如何使用,我的理解是和都Box<T>
可以Rc<T>
透明地强制为&T
,并且&T
是一种原始类型,编译器或多或少希望它要么是指向单个数据单元的指针,要么是指向数据数组的指针和长度,但在那种情况下,Deref
实现让我感到困惑。
impl<T: ?Sized, A: Allocator> Deref for Rc<T, A> {
type Target = T;
#[inline(always)]
fn deref(&self) -> &T {
&self.inner().value
}
}
我感觉我本以为这会以某种方式“丢失”关于长度的信息,因为这些信息包含在 中,Rc
而不是RcInner
或值 中T
。但事实并非如此,编译器神奇地知道,因为Rc<[T]>
本身是一个指针,就像 一样&[T]
,它知道 的Rc
地址后面也有一个隐藏的长度,尽管Rc
它从未明确包含它。我知道长度并不[T]
像你想象的那样存储在 中,而是存储在 的地址堆栈中[T]
,无论是通过Box
、Rc
还是简单的引用。
所以这是我的两个问题:如何在数据结构本身中定义/建立胖指针的附加数据,以及 Rust 如何知道像Rc
或Box
这样的类型不仅仅是一个指针,而是一个具有长度附加信息的胖指针?
这里有一些类似的问题,它们帮助我理解切片/指针/胖指针如何作为背景工作: