将数组分配给* mut c_void

时间:2015-08-01 07:49:45

标签: c rust ffi

我正在为一个库编写绑定,其中我在Rust中有一个类型为void*又名*mut c_void的参数的函数。我必须为这个参数分配一个数组,我怎么能在Rust中做到这一点?

我尝试过投射,transmute,它不起作用(transmutec_void[u8]的大小不同)。如果重要的话,我会从矢量中获取切片。

UPDATE :也许以某种方式使用vec.as_mut_ptr()代替是正确的?

PLAYPEN http://is.gd/KjgduZ

1 个答案:

答案 0 :(得分:6)

您所描述的API看起来非常可疑。请记住,实际上没有"数组"在C - 数组中,只是指向连续放置在内存中的同一类型的多个值的开头的指针的另一个名称。因此,不可能只是分配" C中的数组。有两个概念可以理解为"分配"到数组:首先,在某处指定一个指向数组开头的指针:

const char *s1 = "hello";
const char *s2 = "world";

const char *s = s1;  // make `s` contain a pointer to "hello"
s = s2;  // make `s` contain a pointer to "world"

其次,它是将某些数据从一个指针复制到另一个指针,通常使用memcpy()或类似的东西:

const char *s1 = "hello";

char s2[5];
memcpy(s2, s1, 5);  // copy 5 bytes from the memory pointed at by `s1` to the memory pointed at by `s2`

当我说你的API可疑时,你现在可以看到我的意思。您的回调函数被赋予void *,但是,没有任何指示"数组复制"应该使用方法。

如果它是第一个,即将指针复制到数组的开头,则void *类型非常无用。它没有说明如何表示这个指针。看起来你正试图做到这一点;但是,它可能会像你想象的那样无法工作。以下是代码的编译变体(请注意,这是错误的,很可能会导致程序崩溃;请参阅下文):

#![feature(libc)]
extern crate libc;

use libc::c_void;

pub extern fn demo(data: *mut *mut c_void) {
    let mut vec = vec!(1, 2, 3);
    unsafe {
        *data = vec.as_mut_ptr() as *mut c_void;
    }
}

(请注意,由于autoderef,您可以直接在包含向量的as_mut_ptr()变量上调用mut

参数类型现在不只是*mut c_void而是*mut *mut c_void,也就是说,它是指向*mut c_void的指针。这样调用此函数的程序可以将指向类型为void *的局部变量的指针传递给此函数,并获取指向实际数组的指针,如

void *data;
some_struct.callback_fn(&data);  // pointer to `demo` is stored in `some_struct`
// data is now whatever your `demo` function has assigned

请注意,您只是无法明智地让demo只接受*mut c_void,因为您唯一能做的就是重新分配参数本身,但是重新分配参数将仅重新分配此参数值,即此参数表示的局部变量。这不能在功能之外观察到。换句话说,以下代码(也是您提供的代码的变体):

pub extern fn demo(mut data: *mut c_void) {
    let mut vec = vec!(1, 2, 3);
    data = vec.as_mut_ptr() as *mut c_void;
}

什么都不做,Rust很高兴指出这一点:

<anon>:6:20: 6:28 warning: variable `data` is assigned to, but never used, #[warn(unused_variables)] on by default
<anon>:6 pub extern fn demo(mut data: *mut c_void) {
                            ^~~~~~~~
<anon>:8:5: 8:9 warning: value assigned to `data` is never read, #[warn(unused_assignments)] on by default
<anon>:8     data = vec.as_mut_ptr() as *mut c_void;
             ^~~~

我说*mut *mut c_void的代码错误的原因是它实际上违反了内存安全性。如果您创建一个Vec实例并将其存储到局部变量,当此变量超出范围时,矢量本身将被销毁,并且它将包装的内存将被释放。因此,使用as_ptr()as_mut_ptr()从中获取的每个指针都将无效。

有几种方法可以解决这个问题,最简单的方法就是forget()向量:

use std::mem;

let mut vec = vec![1, 2, 3];
*data = vec.as_mut_ptr() as *mut c_void;
mem::forget(vec);

这样,矢量被遗忘了#34; - 它的析构函数不会被称为。但是,这会导致程序中出现内存泄漏。每次调用demo()时,将分配更多内存但不会释放,因此最终您的程序将使用所有可用内存,之后可能会崩溃。但是,在某些情况下,这是一件明智的事情,特别是在低级代码中。例如,您的API可能会指定它只会调用此函数一次。

这种方法的另一个问题是上述方法的逻辑结果。您的API可以指定谁应该在提供给它的指针处释放内存。例如,它可能需要传递一个分配有malloc()的内存,因此它将自己用free()释放它。或者它可以指定您应该定义另一个函数,当应该释放所有分配的内存时将调用该函数。无论哪种方式在Rust中实现都有些不方便;除非确实如此,否则我不会详细介绍如何操作。无论如何,您的API必须明确指定内存的所有者,并且您应该将其考虑在内,因为Rust对所有权更加明确。

另一种可能性是您的API要求您将一些数据复制到void *指针指定的内存中。换句话说,它的实现包含类似这样的代码:

char buffer[256];
some_struct.callback_fn(buffer);

并且预计在callback_fn次调用后,buffer会填充数据。

如果是这种情况,API自然必须指定程序可能使用的缓冲区中的最大字节数,并且demo函数可能如下所示:

use std::ptr;
use libc::c_void;

pub extern fn demo(data: *mut c_void) {
    let vec: Vec<u8> = vec!(1, 2, 3);
    unsafe { 
        ptr::copy_nonoverlapping(vec.as_ptr(), data as *mut u8, vec.len());
    }
}

(或者,您可以使用std::slice::from_raw_parts_mut()data转换为&mut [u8]并使用clone_from_slice()方法或bytes::copy_memory()函数,但它们都不稳定,因此它们不能用于稳定的Rust)

在这种情况下,您应特别注意不要将调用程序提供的缓冲区溢出给您。其最大大小应在API中指定。

另一个问题是复制数组只对字节数组很简单(C侧为char *,Rust方为&[u8]/&mut [u8]。当您开始使用较大类型(如i32)时,您将有可能出现可移植性问题。例如,在C int中没有定义的尺寸,因此您无法盲目地将&[i32]转换为&[u8],原始尺寸为原始尺寸的四倍从它到*mut u8的字节数。这些问题应该非常小心。