如何从 Box<Trait> 获取指向虚拟表的指针?

Dan*_*ila 2 pointers vtable rust

我想了解虚拟表的工作原理并可能在将来使用它。我知道DST指针是胖指针(usize,usize),其中第一个指针是数据,第二个指针是虚拟表。我能够通过指针获取数据,但显然它没有\xe2\x80\x99无法获取指向虚拟表的指针,因为来自https://docs.google.com/presentation/d/1q- c7UAyrUlM-eZyTo1pd8SZ0qwA_wYxmPZVOQkoDmH4/edit#slide=id.p结构体的大小应该由下面代码中的size变量中的指针存储,但事实并非如此。也许我没有正确理解虚拟表是如何存储的?

\n

我的代码:

\n
#![feature(pointer_byte_offsets)]\n\ntrait MyRule {\n    fn some_method1(&self) {}\n    fn some_method2(&self) {}\n}\nstruct MyData {\n    data: u32,\n}\n\nimpl MyRule for MyData {}\n\nfn main() {\n    unsafe {\n        let b: Box<dyn MyRule> = Box::new(MyData { data: 5 });\n        let fat_ptr = Box::into_raw(b);\n        dbg!(*(fat_ptr.cast::<u32>())); // data = 5\n        let vtbl_ptr = fat_ptr.byte_add(8).cast::<*mut usize>();\n        let destructor_ptr = (*vtbl_ptr).cast::<usize>();\n        let size = destructor_ptr.byte_add(8);\n        dbg!(size);\n        dbg!(*size); // Not size!!!\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我认为问题在于我没有正确获取指向表本身的指针。那么正确的方法是什么?如何访问表元素?

\n

Pit*_*taJ 5

我不确定您是否打算这样做,但您的代码的行为就好像 vtable 指针位于结构旁边的堆上一样。事实上,虚函数表指针位于堆栈上的其他指针旁边。

Box<dyn T>本质上相当于(*mut {data}, *mut {vtable}),但是当您尝试像这样访问 vtable 指针时:

        let fat_ptr = Box::into_raw(b);
        dbg!(*(fat_ptr.cast::<u32>())); // data = 5
        let vtbl_ptr = fat_ptr.byte_add(8).cast::<*mut usize>();
Run Code Online (Sandbox Code Playgroud)

正在byte_add偏移指向堆上结构MyData数字,而不是移动到堆栈上的其他位置。然后.cast::<*mut usize>()说将该新数字解释为指向a 的指针usize(又名 *mut *mut usize)。

相反,您真正想做的是解构该Box结构以获取这两个指针:

let (pointer, vtable) = mem::transmute_copy::<Box<_>, (*const u8, *const usize)>(&b);
Run Code Online (Sandbox Code Playgroud)

然后你只需使用 useize 偏移 1 即可获得指向 的指针size

let size_ptr = vtable.add(1); // size is offset by 1 usize
Run Code Online (Sandbox Code Playgroud)

完整示例:

use std::mem;

trait MyRule {
    fn some_method1(&self) {}
    fn some_method2(&self) {}
}
struct MyData {
    data: u32,
}

impl MyRule for MyData {}

fn main() {
    unsafe {
        let b: Box<dyn MyRule> = Box::new(MyData { data: 5 });
        assert_eq!(mem::size_of_val(&b), mem::size_of::<usize>()*2);
        
        // copy pointers from Box
        let (pointer, vtable) = mem::transmute_copy::<Box<_>, (*const u8, *const usize)>(&b);
        assert_eq!(pointer as usize, &*b as *const _ as *const u8 as usize);
        dbg!(pointer, &*b as *const _);
        
        let size_ptr = vtable.add(1); // size is offset by 1 usize

        assert_eq!(*size_ptr, mem::size_of::<MyData>());
        dbg!(*size_ptr, mem::size_of::<MyData>());
    }
}
Run Code Online (Sandbox Code Playgroud)

操场