什么是“胖指针"? [英] What is a "fat pointer"?

查看:179
本文介绍了什么是“胖指针"?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经在多个上下文中阅读过术语胖指针",但我不确定它的确切含义以及何时在 Rust 中使用.指针似乎是普通指针的两倍大,但我不明白为什么.它似乎也与 trait 对象有关.

I've read the term "fat pointer" in several contexts already, but I'm not sure what exactly it means and when it is used in Rust. The pointer seems to be twice as large as a normal pointer, but I don't understand why. It also seems to have something to do with trait objects.

推荐答案

术语胖指针"用于引用对动态大小类型 (DST) – 切片或特征对象的引用和原始指针. 胖指针包含一个指针和一些使 DST完整"的信息.(例如长度).

The term "fat pointer" is used to refer to references and raw pointers to dynamically sized types (DSTs) – slices or trait objects. A fat pointer contains a pointer plus some information that makes the DST "complete" (e.g. the length).

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

Most commonly used types in Rust are not DSTs, but have a fixed size known at compile time. These types implement the Sized trait. Even types that manage a heap buffer of dynamic size (like Vec<T>) are Sized as the compiler knows the exact number of bytes a Vec<T> instance will take up on the stack. There are currently four different kinds of DSTs in Rust.

类型 [T](对于任何 T)是动态调整大小的(特殊的字符串切片"类型 str 也是如此).这就是为什么您通常只将其视为 &[T]&mut [T],即在引用后面.该引用是所谓的胖指针".让我们检查一下:

The type [T] (for any T) is dynamically sized (so is the special "string slice" type str). That's why you usually only see it as &[T] or &mut [T], i.e. behind a reference. This reference is a so-called "fat pointer". Let's check:

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

这会打印(经过一些清理):

This prints (with some cleanup):

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

所以我们看到对像 u32 这样的普通类型的引用是 8 字节大,对数组 [u32; 的引用也是如此.2].这两种类型不是 DST.但是由于 [u32] 是 DST,对它的引用是两倍大.在切片的情况下,完成"的附加数据是DST 只是长度. 所以可以说 &[u32] 的表示是这样的:

So we see that a reference to a normal type like u32 is 8 bytes large, as is a reference to an array [u32; 2]. Those two types are not DSTs. But as [u32] is a DST, the reference to it is twice as large. In the case of slices, the additional data that "completes" the DST is simply the length. So one could say the representation of &[u32] is something like this:

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


Trait 对象 (dyn Trait)

当使用 trait 作为 trait 对象(即类型擦除、动态调度)时,这些 trait 对象是 DST.示例:


Trait objects (dyn Trait)

When using traits as trait objects (i.e. type erased, dynamically dispatched), these trait objects are DSTs. Example:

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

这会打印(经过一些清理):

This prints (with some cleanup):

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

同样,&Cat 只有 8 字节大,因为 Cat 是普通类型.但是 dyn Animal 是一个 trait 对象,因此可以动态调整大小.因此,&dyn Animal 大小为 16 字节.

Again, &Cat is only 8 bytes large because Cat is a normal type. But dyn Animal is a trait object and therefore dynamically sized. As such, &dyn Animal is 16 bytes large.

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

In the case of trait objects, the additional data that completes the DST is a pointer to the vtable (the vptr). I cannot fully explain the concept of vtables and vptrs here, but they are used to call the correct method implementation in this virtual dispatch context. The vtable is a static piece of data that basically only contains a function pointer for each method. With that, a reference to a trait object is basically represented as:

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

(这与 C++ 不同,C++ 中抽象类的 vptr 存储在对象中.两种方法都有优点和缺点.)

(This is different from C++, where the vptr for abstract classes is stored within the object. Both approaches have advantages and disadvantages.)

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

It's actually possible to create your own DSTs by having a struct where the last field is a DST. This is rather rare, though. One prominent example is std::path::Path.

自定义 DST 的引用或指针也是一个胖指针.附加数据取决于结构内的 DST 类型.

A reference or pointer to the custom DST is also a fat pointer. The additional data depends on the kind of DST inside the struct.

RFC 1861 中,引入了 extern type 功能.Extern 类型也是 DST,但指向它们的指针不是胖指针.或者更准确地说,正如 RFC 所说:

In RFC 1861, the extern type feature was introduced. Extern types are also DSTs, but pointers to them are not fat pointers. Or more exactly, as the RFC puts it:

在 Rust 中,指向 DST 的指针携带有关所指向对象的元数据.对于字符串和切片,这是缓冲区的长度,对于 trait 对象,这是对象的 vtable.对于 extern 类型,元数据只是 ().这意味着指向 extern 类型的指针与 usize 具有相同的大小(即,它不是胖指针").

In Rust, pointers to DSTs carry metadata about the object being pointed to. For strings and slices this is the length of the buffer, for trait objects this is the object's vtable. For extern types the metadata is simply (). This means that a pointer to an extern type has the same size as a usize (ie. it is not a "fat pointer").

但如果您不与 C 接口交互,您可能永远不必处理这些 extern 类型.

But if you are not interacting with a C interface, you probably won't ever have to deal with these extern types.

在上面,我们已经看到了不可变引用的大小.胖指针对于可变引用、不可变原始指针和可变原始指针的作用相同:

Above, we've seen the sizes for immutable references. Fat pointers work the same for mutable references, immutable raw pointers and mutable raw pointers:

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

这篇关于什么是“胖指针"?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆