什么是“脂肪指针”?

我已经在几个上下文中读到过“胖指针”这个术语,但是我不确定它到底是什么意思,以及在 Rust 中什么时候使用它。指针似乎是普通指针的两倍大,但我不明白为什么。它似乎还与 trait 对象有关。

18466 次浏览

术语“胖指针”用于指向 动态调整大小的类型(DST)-片或特征对象的引用和原始指针。一个胖指针包含一个指针加上一些使 DST“完整”的信息(例如长度)。

Rust 中最常用的类型是 没有 DST,但在编译时具有已知的固定大小。这些类型实现 Sized特性。即使是管理动态大小堆缓冲区(如 Vec<T>)的类型也是 Sized,因为编译器知道 Vec<T>实例将在堆栈上占用的确切字节数。目前 Rust 中有四种不同类型的 DST。


切片([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]的引用也是如此。这两种类型不是 DST。但是因为 [u32]是 DST,所以对它的引用是它的两倍大。所以可以说 &[u32]的表示是这样的:

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

特征对象(dyn Trait)

当使用 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是一个 trait 对象,因此可以动态调整大小。因此,&dyn Animal大小为16字节。

对于 trait 对象,完成 DST 的附加数据是指向 vtable (vptr)的指针。这里我不能完全解释 vtables 和 vptrs 的概念,但是它们用于调用这个虚拟分派上下文中的正确方法实现。Vtable 是一个静态数据块,基本上只包含每个方法的函数指针。因此,对 trait 对象的引用基本上表示为:

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

(这不同于 C + + ,在 C + + 中,抽象类的 vptr 存储在对象中。这两种方法各有优缺点。)


定制 DST

实际上可以通过使用一个 struct 来创建自己的 DST,其中最后一个字段是 DST。不过这种情况相当罕见。一个突出的例子是 std::path::Path

指向自定义 DST 的引用或指针也是胖指针。额外的数据取决于结构中 DST 的类型。


例外: 外部类型

RFC 1861中,引入了 extern type特性。外部类型也是 DST,但指向它们的指针是 没有胖指针。或者更确切地说,正如 RFC 所言:

在 Rust 中,指向 DST 的指针携带关于所指向对象的元数据。对于字符串和切片,这是缓冲区的长度,对于 trait 对象,这是对象的 vtable。对于外部类型,元数据只是 ()。这意味着指向外部类型的指针的大小与 usize相同(即。它不是一个“肥指针”)。

但是如果您没有与 C 接口进行交互,那么您可能永远不需要处理这些外部类型。




在上面,我们已经看到了不可变引用的大小。胖指针对可变引用、不可变原始指针和可变原始指针的工作原理是一样的:

size_of::<&[u32]>()       = 16
size_of::<&mut [u32]>()   = 16
size_of::<*const [u32]>() = 16
size_of::<*mut [u32]>()   = 16