rust 什么是“胖指针”?

1yjd4xko  于 2023-10-20  发布在  其他
关注(0)|答案(2)|浏览(119)

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

enxuqcxy

enxuqcxy1#

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

Rust中最常用的类型是 not DST,但在编译时有一个固定的大小。这些类型实现the Sized trait。即使是管理动态大小的堆缓冲区的类型(如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]的引用也是8个字节。这两种类型都不是DST。但由于[u32]是DST,因此对它的引用是其两倍大。**在切片的情况下,“完成”DST的额外数据只是长度。**所以可以说&[u32]的表示是这样的:

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

属性对象(dyn Trait

当使用trait作为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)的指针。**我不能在这里完全解释vtable和vptr的概念,但它们用于在这个虚拟分派上下文中调用正确的方法实现。vtable是一个静态数据块,基本上只包含每个方法的函数指针。这样,对trait对象的引用基本上表示为:

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

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

自定义DST

实际上可以通过最后一个字段是DST的结构来创建自己的DST。不过,这种情况相当罕见。一个突出的例子是std::path::Path
指向自定义DST的引用或指针也是胖指针。额外的数据取决于结构内部的DST类型。

异常:外部类型

RFC 1861中,引入了extern type特性。外部类型也是DST,但指向它们的指针是 * 非 * 胖指针。或者更确切地说,正如RFC所说:
在Rust中,指向DST的指针携带关于所指向对象的元数据。对于字符串和切片,这是缓冲区的长度,对于trait对象,这是对象的vtable。对于extern类型,元数据只是()。这意味着指向extern类型的指针与usize(即usize)的大小相同。它不是“胖指针”)。
但是如果你不与C接口交互,你可能永远不必处理这些外部类型。
在上面,我们已经看到了不可变引用的大小。胖指针对于可变引用、不可变原始指针和可变原始指针的工作方式相同:

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

6qfn3psc2#

扩展@Lukas关于vtable或虚拟表的答案。Vtables主要用于trait对象。vtable包含指向实现trait的具体类型的trait方法的指针。举例来说:

struct Sheep {}
struct Cow {}

trait Animal {
    fn method(&self) -> String;
}

impl Animal for Sheep {
    fn method(&self) -> String {
        "baaaaah!".to_string()
    }
}

impl Animal for Cow {
    fn method(&self) -> String {
        "moooooo!".to_string()
    }
}

fn random_animal1(random_string: &str) -> &dyn Animal {
    match random_string {
        "sheep" => &Sheep {},
        "cow" => &Cow {},
        _ => panic!(),
    }
}

fn random_animal2(random_string: &str) -> Box<dyn Animal> {
    match random_string {
        "sheep" => Box::new(Sheep {}),
        "cow" => Box::new(Cow {}),
        _ => panic!(),
    }
}

没有办法知道哪些具体类型将用于实现Animal trait。因此,vtable是在运行时根据对象的实际类型动态确定调用哪个方法。
每当我们使用Trait Objectsdyn关键字时,Rust都会生成一个与该trait相关联的vtable。它通过使用trait对象来启用多态性,而不需要知 prop 体的类型。有盒装Box<dyn Trait>和参考&dyn Trait Trait Object:

Trait对象使用数据指针(ptr)和vtable指针(vptr)的组合。主要的区别是,装箱的trait对象被分配在堆上,Box拥有它。而&dyn是指向trait对象的借用引用。它对基础对象没有所有权。它引用的对象必须由其他实体拥有,它暂时借用它。

相关问题