为什么一个 Box 指针传递给 C 并返回到 Rust 段错误



一些 C 代码调用下面的 Rust open 调用,该调用返回一个指针。 稍后,C 代码将完全相同的指针传递回 close 函数,该函数试图删除(释放(它。 它在free(3)中存在段错误。 为什么?

use std::os::raw::{c_int, c_void};
struct Handle;
extern "C" fn open(_readonly: c_int) -> *mut c_void {
    let h = Handle;
    let h = Box::new(h);
    return Box::into_raw(h) as *mut c_void;
}
extern "C" fn close(h: *mut c_void) {
    let h = unsafe { Box::from_raw(h) };
    // XXX This segfaults - why?
    drop(h);
}

close中,您最终会创建一个Box<c_void>而不是Box<Handle>,因为您在调用Box::from_raw之前没有将*mut c_void转换回*mut Handle

fn close(h: *mut c_void) {
    let h = unsafe { Box::from_raw(h as *mut Handle) };
    drop(h);
}

顺便说一下,Box实际上并没有为零大小的类型分配任何内存(例如此处Handle(,而是使用固定的非零指针值(在当前实现中,这是类型的对齐方式;默认情况下,零大小类型的对齐方式为 1(。盒装零大小类型的析构函数知道不要尝试在此虚构内存地址处释放内存,但c_void不是零大小类型(它的大小为 1(,因此 Box<c_void> 的析构函数尝试在地址 0x1 释放内存,这会导致段错误。

如果Handle不是零大小,那么代码可能不会崩溃,但它仍然会运行错误的析构函数(它会运行c_void析构函数,它什么都不做(,这可能会导致内存泄漏。析构函数Drop::drop为类型运行(如果存在(,然后删除类型的字段。

问题是您在将指针转换回Box时没有将指针转换回Handle指针,并且得到了错误类型的Box

这有效:

fn close(h: *mut c_void) {
    let h = unsafe { Box::from_raw(h as *mut Handle) };
    //                               ^^^^^^^^^^^^^^
    drop(h);
}

在您的代码中,h是一个std::boxed::Box<std::ffi::c_void>

最新更新