您描述的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"
Run Code Online (Sandbox Code Playgroud)
其次,它是将某些数据从一个指针复制到另一个指针,这通常是通过以下方式完成的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`
Run Code Online (Sandbox Code Playgroud)
当我说你的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;
}
}
Run Code Online (Sandbox Code Playgroud)
(请注意,由于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
Run Code Online (Sandbox Code Playgroud)
请注意,您只是不能理智使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;
}
Run Code Online (Sandbox Code Playgroud)
没有做任何事,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;
^~~~
Run Code Online (Sandbox Code Playgroud)
我说代码*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);
Run Code Online (Sandbox Code Playgroud)
这样矢量被"遗忘" - 它的析构函数不会被调用.但是,这会导致程序中出现内存泄漏.随着每次调用demo()更多内存将被分配但未释放,因此最终您的程序将使用所有可用内存,并可能在之后崩溃.但是,在某些情况下,这是一件明智的事情,特别是在低级代码中.例如,您的API可能会指定它只会调用此函数一次.
这种方法的另一个问题是上述方法的逻辑结果.您的API可以指定谁应该在提供给它的指针处释放内存.例如,它可能需要传递一个分配的内存malloc(),然后它将free()自己释放它.或者它可以指定您应该定义另一个函数,当应该释放所有分配的内存时将调用该函数.无论哪种方式在Rust中实现都有些不方便; 除非确实如此,否则我不会详细介绍如何做到这一点.无论如何,您的API必须明确指定内存的所有者,您应该考虑它,因为Rust更清楚地了解所有权.
另一种可能性是您的API要求您将一些数据复制到void *指针指定的内存中.换句话说,它的实现包含如下代码:
char buffer[256];
some_struct.callback_fn(buffer);
Run Code Online (Sandbox Code Playgroud)
并且它希望在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());
}
}
Run Code Online (Sandbox Code Playgroud)
(或者,您可以转换data为&mut [u8]使用std::slice::from_raw_parts_mut()和使用clone_from_slice()方法或bytes::copy_memory()函数,但它们都不稳定,因此它们不能用于稳定的Rust)
在这种情况下,您应该特别小心,不要将调用程序提供的缓冲区溢出给您.其最大大小应在API中指定.
另一个问题是复制数组对于字节数组来说很简单(char *在C端,&[u8]/&mut [u8]在Rust端).当您开始使用较大类型时i32,您将获得可移植性问题的可能性.例如,在C int没有定义大小的情况下,所以你不能盲目地转换&[i32]为&[u8]原始大小的四倍并从中复制字节*mut u8.应该非常小心地处理这些问题.