我已经在几个上下文中读到了“胖指针”这个术语,但我不确定它到底是什么意思,以及它在Rust中的使用时间。指针似乎是普通指针的两倍大,但我不明白为什么。它似乎也与trait对象有关。
enxuqcxy1#
**术语“胖指针”用于指指向 * 动态大小类型 *(DST)-切片或特征对象的引用和原始指针。**胖指针包含一个指针加上一些使DST“完整”的信息(例如:长度)。
Rust中最常用的类型是 not DST,但在编译时有一个固定的大小。这些类型实现the Sized trait。即使是管理动态大小的堆缓冲区的类型(如Vec<T>)也是Sized,因为编译器知道Vec<T>示例将在堆栈上占用的确切字节数。目前Rust中有四种不同的DST。
Sized
Vec<T>
[T]
str
类型[T](对于任何T)是动态调整大小的(特殊的“字符串切片”类型str也是如此)。这就是为什么你通常只看到它为&[T]或&mut [T],即。背后的参考。这个引用就是所谓的“胖指针”。让我们检查一下:
T
&[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]的表示是这样的:
u32
[u32; 2]
[u32]
&[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字节大。
&Cat
Cat
dyn Animal
&dyn Animal
**在trait对象的情况下,完成DST的附加数据是指向vtable(vptr)的指针。**我不能在这里完全解释vtable和vptr的概念,但它们用于在这个虚拟分派上下文中调用正确的方法实现。vtable是一个静态数据块,基本上只包含每个方法的函数指针。这样,对trait对象的引用基本上表示为:
struct TraitObjectRef { data_ptr: *const (), vptr: *const (), }
(This与C不同,在C中,抽象类的vptr存储在对象中。这两种方法都有优点和缺点)。
实际上可以通过最后一个字段是DST的结构来创建自己的DST。不过,这种情况相当罕见。一个突出的例子是std::path::Path。指向自定义DST的引用或指针也是胖指针。额外的数据取决于结构内部的DST类型。
std::path::Path
在RFC 1861中,引入了extern type特性。外部类型也是DST,但指向它们的指针是 * 非 * 胖指针。或者更确切地说,正如RFC所说:在Rust中,指向DST的指针携带关于所指向对象的元数据。对于字符串和切片,这是缓冲区的长度,对于trait对象,这是对象的vtable。对于extern类型,元数据只是()。这意味着指向extern类型的指针与usize(即usize)的大小相同。它不是“胖指针”)。但是如果你不与C接口交互,你可能永远不必处理这些外部类型。在上面,我们已经看到了不可变引用的大小。胖指针对于可变引用、不可变原始指针和可变原始指针的工作方式相同:
extern type
()
usize
size_of::<&[u32]>() = 16 size_of::<&mut [u32]>() = 16 size_of::<*const [u32]>() = 16 size_of::<*mut [u32]>() = 16
6qfn3psc2#
扩展@Lukas关于vtable或虚拟表的答案。Vtables主要用于trait对象。vtable包含指向实现trait的具体类型的trait方法的指针。举例来说:
vtable
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 Objects,dyn关键字时,Rust都会生成一个与该trait相关联的vtable。它通过使用trait对象来启用多态性,而不需要知 prop 体的类型。有盒装Box<dyn Trait>和参考&dyn Trait Trait Object:
Animal
dyn
Box<dyn Trait>
&dyn Trait
Trait对象使用数据指针(ptr)和vtable指针(vptr)的组合。主要的区别是,装箱的trait对象被分配在堆上,Box拥有它。而&dyn是指向trait对象的借用引用。它对基础对象没有所有权。它引用的对象必须由其他实体拥有,它暂时借用它。
ptr
vptr
Box
&dyn
2条答案
按热度按时间enxuqcxy1#
**术语“胖指针”用于指指向 * 动态大小类型 *(DST)-切片或特征对象的引用和原始指针。**胖指针包含一个指针加上一些使DST“完整”的信息(例如:长度)。
Rust中最常用的类型是 not DST,但在编译时有一个固定的大小。这些类型实现the
Sized
trait。即使是管理动态大小的堆缓冲区的类型(如Vec<T>
)也是Sized
,因为编译器知道Vec<T>
示例将在堆栈上占用的确切字节数。目前Rust中有四种不同的DST。切片(
[T]
和str
)类型
[T]
(对于任何T
)是动态调整大小的(特殊的“字符串切片”类型str
也是如此)。这就是为什么你通常只看到它为&[T]
或&mut [T]
,即。背后的参考。这个引用就是所谓的“胖指针”。让我们检查一下:这将打印(经过一些清理):
因此,我们可以看到,对普通类型(如
u32
)的引用是8个字节,对数组[u32; 2]
的引用也是8个字节。这两种类型都不是DST。但由于[u32]
是DST,因此对它的引用是其两倍大。**在切片的情况下,“完成”DST的额外数据只是长度。**所以可以说&[u32]
的表示是这样的:属性对象(
dyn Trait
)当使用trait作为trait对象时(即类型擦除,动态分派),这些trait对象是DST。范例:
这将打印(经过一些清理):
同样,
&Cat
只有8个字节大,因为Cat
是一个普通类型。但是dyn Animal
是一个trait对象,因此可以动态调整大小。因此,&dyn Animal
是16字节大。**在trait对象的情况下,完成DST的附加数据是指向vtable(vptr)的指针。**我不能在这里完全解释vtable和vptr的概念,但它们用于在这个虚拟分派上下文中调用正确的方法实现。vtable是一个静态数据块,基本上只包含每个方法的函数指针。这样,对trait对象的引用基本上表示为:
(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接口交互,你可能永远不必处理这些外部类型。
在上面,我们已经看到了不可变引用的大小。胖指针对于可变引用、不可变原始指针和可变原始指针的工作方式相同:
6qfn3psc2#
扩展@Lukas关于
vtable
或虚拟表的答案。Vtables主要用于trait对象。vtable包含指向实现trait的具体类型的trait方法的指针。举例来说:没有办法知道哪些具体类型将用于实现
Animal
trait。因此,vtable是在运行时根据对象的实际类型动态确定调用哪个方法。每当我们使用Trait Objects,
dyn
关键字时,Rust都会生成一个与该trait相关联的vtable。它通过使用trait对象来启用多态性,而不需要知 prop 体的类型。有盒装Box<dyn Trait>
和参考&dyn Trait
Trait Object:Trait对象使用数据指针(
ptr
)和vtable指针(vptr
)的组合。主要的区别是,装箱的trait对象被分配在堆上,Box
拥有它。而&dyn
是指向trait对象的借用引用。它对基础对象没有所有权。它引用的对象必须由其他实体拥有,它暂时借用它。