【问题标题】:How to obtain address of trait object?如何获取特征对象的地址?
【发布时间】:2020-04-23 03:34:28
【问题描述】:

如何获取 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
}

但它会产生不同的地址。

【问题讨论】:

  • 第二行给出了Box 的地址,而不是它的内容。但是,我不确定第三行:看起来它只是失败了,但我不明白为什么。

标签: rust


【解决方案1】:

首先,简单一点:如 cmets 中所述,&amp;boxed_func 只是局部变量boxed_func 的地址,而不是底层数据的地址。将其视为指向指针的指针。

现在是困难的部分。我认为让您感到困惑的一件事是指向特征对象的指针是胖指针,而使用"{: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
}

(playground link)

所以boxed_func 的实际底层指针由两个指针组成:0x10x55ec289004c8(结果可能在此处有所不同)。 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
}

(playground link)

所以在我们使用 trait 对象的情况下,这告诉我们 trait 对象的数据部分(可能)是零大小的类型,这是有道理的,因为 func 没有任何关联的数据除了需要调用它之外。该信息保存在vtable


查看特征对象原始部分的更安全(更少 UB 诱导)的方法是使用仅夜间结构 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
}

(playground link)

用其他一些特征对象试试这个,看看你是否能找到一些没有零大小数据的对象。


TraitObject 已弃用,根据Tracking Issue for pointer metadata APIs #81513,它被to_raw_parts(self) method of primitive type pointer 替换。


#![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);
}

playground link

【讨论】:

  • “对 dyn Fn() 来说唯一重要的是函数的作用” - 这里就是这种情况,因为所讨论的函数就是函数。然而,闭包就不是这样了。
  • “但是,闭包就不是这样了。” indeed 虽然应该注意不要混淆“匿名函数”和“闭包”。
  • 似乎TraitObject 已被弃用,根据here,它已被to_raw_parts(self) method of primitive type pointer 替换。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-27
  • 2017-11-21
  • 1970-01-01
相关资源
最近更新 更多