如何获取 trait 对象的地址?我试过这个:
fn func() {}
fn main() {
let boxed_func: Box<dyn Fn()> = Box::new(func);
println!("expect: {:p}", func as *const ()); // 0x55bb0207e570
println!("actual: {:p}", &boxed_func); // 0x7ffe5217e5a0
println!("actual: {:p}", Box::into_raw(boxed_func)); // 0x1
}
Run Code Online (Sandbox Code Playgroud)
但它产生不同的地址。
首先,简单的:正如评论中提到的,&boxed_func只是局部变量boxed_func的地址,而不是底层数据的地址。将其视为指向指针的指针。
现在是困难的部分。我认为让您感到困惑的一件事是指向 trait 对象的指针是胖指针,而这并不能通过使用"{:p}". 它们由一个指向实际数据的指针和一个指向 vtable 的指针组成,vtable 存储有关 trait 的实现的信息。
这可以通过(可能是 UB)代码看到
fn func() {}
fn main() {
let boxed_func: Box<dyn Fn()> = Box::new(func);
println!("function pointer: {:p}", func as fn()); // 0x560ae655d1a0
println!("trait object data pointer: {:p}", boxed_func); // 0x1
println!("stack pointer: {:p}", &boxed_func); // 0x7ffebe8f4688
let raw = Box::into_raw(boxed_func);
println!("raw data pointer: {:p}", raw); // 0x1
// This is likely undefined behavior, since I believe the layout of trait objects isn't specified
let more_raw = unsafe { std::mem::transmute::<_, (usize, usize)>(raw) };
println!("full fat pointer: {:#x}, {:#x}", more_raw.0, more_raw.1); // 0x1, 0x560ae6789468
}
Run Code Online (Sandbox Code Playgroud)
所以实际的底层指针boxed_func由两个指针组成:0x1和0x55ec289004c8(结果可能在这里有所不同)。0x1是指向零大小类型的指针的常用值。显然,您不想为此使用空指针,但您也不需要一个有效的指针。零大小类型通常使用 分配Unique::empty,它只是返回一个悬空指针,指向类型对齐处的内存位置(零大小类型的对齐方式为 1)。
// Some zero-sized types and where they get allocated
struct Foo;
fn main() {
let x = Box::new(());
println!("{:p}", x); // 0x1
let y = Box::new(Foo);
println!("{:p}", y); // 0x1
}
Run Code Online (Sandbox Code Playgroud)
因此,在我们使用 trait 对象的情况下,这告诉我们 trait 对象的数据部分(可能)是一个零大小的类型,这是有道理的,因为func除了需要调用的数据之外没有任何关联的数据它。该信息保存在vtable.
查看 trait 对象的原始部分的一种更安全(更少 UB 诱导)的方法是使用 nightly-only struct TraitObject。
#![feature(raw)]
use std::raw::TraitObject;
fn func() {}
fn main() {
let boxed_func: Box<dyn Fn()> = Box::new(func);
println!("function: {:p}", func as fn()); // 0x56334996e850
println!("function trait object: {:p}", boxed_func); // 0x1
println!("stack address: {:p}", &boxed_func); // 0x7ffee04c2378
// Safety: `Box<dyn Trait>` is guaranteed to have the same layout as `TraitObject`.
let trait_object = unsafe { std::mem::transmute::<_, TraitObject>(boxed_func) };
println!("data pointer: {:p}", trait_object.data); // 0x1
println!("vtable pointer: {:p}", trait_object.vtable); // 0x563349ba3068
}
Run Code Online (Sandbox Code Playgroud)
用其他一些特征对象试试这个,看看你是否能找到一些没有零大小数据的对象。
TraitObject已弃用,根据指针元数据 API 的跟踪问题 #81513,它被替换to_raw_parts(self)为原始类型指针的方法。
#![feature(ptr_metadata)]
trait Trait {
fn f(&self) -> i32;
}
struct Struct {
i: i32
}
impl Trait for Struct {
fn f(&self) -> i32 {
self.i
}
}
fn main() {
let s = Struct { i: 1 };
let sp = &s as *const _;
let (sdynp, sdynvtable) = (&s as &dyn Trait as *const dyn Trait).to_raw_parts();
println!("sp = {:p}", sp);
println!("sdynp = {:p}, sdynvtable = {:#?}", sdynp, sdynvtable);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
160 次 |
| 最近记录: |