Rust中使用Box::from_raw()和Box::into_raw()来回转换指针时出现无效指针问题

4

我一直在摆弄这个使用看似简单的Box函数,试图创建一些FFI辅助代码。

这里的示例似乎在与具有字段的结构体一起使用时会报错:free(): invalid pointer

pub struct Handle(usize);

impl Handle {
    pub fn from<T>(obj: T) -> Self {
        let boxed = Box::new(obj);
        let mut ptr = Box::into_raw(boxed);
        Self::from_ptr_mut(&mut ptr)
    }

    pub fn from_ptr_mut<T>(ptr: &mut T) -> Self {
        Self(ptr as *mut T as usize)
    }

    pub fn to_box<T>(self) -> Box<T> {
        let obj: *mut T = self.to_ptr_mut();
        unsafe { Box::from_raw(obj) }
    }

    pub fn to_ptr_mut<T>(self) -> *mut T {
        self.0 as *mut T
    }
}

#[allow(dead_code)]
struct Crashes { value: u64 }

impl Drop for Crashes {
    fn drop(&mut self) {
        println!("Crashes dropped");
    }
}

fn crashes() {
    let t = Crashes { value: 12 };
    let a = Handle::from(t);
    let b = a.to_box::<Crashes>();
    drop(b);
}

struct Works;

impl Drop for Works {
    fn drop(&mut self) {
        println!("Works dropped");
    }
}

fn works() {
    let t = Works;
    let a = Handle::from(t);
    let b = a.to_box::<Works>();
    drop(b);
}

fn main() {
    works();
    crashes();
}

你可以将此代码粘贴到https://play.rust-lang.org/中,看它如何抛出错误free(): invalid pointer
看起来,drop函数在适当的时间被调用,但指针似乎有些无效。
1个回答

8
你在这里创建了一个双指针:
impl Handle {
    pub fn from<T>(obj: T) -> Self {
        let boxed = Box::new(obj);
        let mut ptr = Box::into_raw(boxed);
        Self::from_ptr_mut(&mut ptr)
    }

    pub fn from_ptr_mut<T>(ptr: &mut T) -> Self {
        Self(ptr as *mut T as usize)
    }
    ...
}

Box::into_raw返回一个指针,但是您却对该指针取了一个可变引用,并将该地址存储为usize。您应该只使用Box::into_raw返回的*mut T
不起作用代码出现双重指针的原因是from<T>from_ptr_mut<T>可以使用完全不同的T参数。如果我们将传递给from<T>的类型T视为具体类型,则在此情况下,您正在使用类型为&mut *mut T的参数调用from_ptr_mut<U>(其中U*mut T)。
正确的实现如下:
impl Handle {
    pub fn from<T>(obj: T) -> Self {
        let boxed = Box::new(obj);
        let ptr = Box::into_raw(boxed);
        Self::from_ptr_mut(ptr)
    }

    pub fn from_ptr_mut<T>(ptr: *mut T) -> Self {
        Self(ptr as usize)
    }
    ...
}

在这里看到示例。


虽然我们处于unsafe领域,但您可以让编译器为您完成一些工作,方法是将参数T绑定到您的Handle结构上。这样,您将被静态防止加载不同于存储的类型。

Playground示例,其中Handle包括PhantomData。

在第二个示例中,您不必像a.to_box::<Crashes>()那样告诉编译器要检索哪个项目,这是很好的,因为您不能通过指定错误的类型引入未定义的行为。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接