我正在尝试释放分配给 CString
的内存并使用ctypes传递给Python . 但是,Python崩溃了一个malloc错误:
python(30068,0x7fff73f79000) malloc: *** error for object 0x103be2490: pointer being freed was not allocated
这是我用来将指针传递给ctypes的Rust函数:
#[repr(C)]
pub struct Array {
pub data: *const c_void,
pub len: libc::size_t,
}
// Build &mut[[f64; 2]] from an Array, so it can be dropped
impl<'a> From<Array> for &'a mut [[f64; 2]] {
fn from(arr: Array) -> Self {
unsafe { slice::from_raw_parts_mut(arr.data as *mut [f64; 2], arr.len) }
}
}
// Build an Array from a Vec, so it can be leaked across the FFI boundary
impl<T> From<Vec<T>> for Array {
fn from(vec: Vec<T>) -> Self {
let array = Array {
data: vec.as_ptr() as *const libc::c_void,
len: vec.len() as libc::size_t,
};
mem::forget(vec);
array
}
}
// Build a Vec from an Array, so it can be dropped
impl From<Array> for Vec<[f64; 2]> {
fn from(arr: Array) -> Self {
unsafe { Vec::from_raw_parts(arr.data as *mut [f64; 2], arr.len, arr.len) }
}
}
// Decode an Array into a Polyline
impl From<Array> for String {
fn from(incoming: Array) -> String {
let result: String = match encode_coordinates(&incoming.into(), 5) {
Ok(res) => res,
// we don't need to adapt the error
Err(res) => res
};
result
}
}
#[no_mangle]
pub extern "C" fn encode_coordinates_ffi(coords: Array) -> *mut c_char {
let s: String = coords.into();
CString::new(s).unwrap().into_raw()
}
我用它来释放指针,当它被Python返回时
pub extern "C" fn drop_cstring(p: *mut c_char) {
unsafe { CString::from_raw(p) };
}
我正在使用Python函数将指针转换为 str
:
def char_array_to_string(res, _func, _args):
""" restype is c_void_p to prevent automatic conversion to str
which loses pointer access
"""
converted = cast(res, c_char_p)
result = converted.value
drop_cstring(converted)
return result
我用来生成 Array
结构的Python函数传递给Rust:
class _FFIArray(Structure):
"""
Convert sequence of float lists to a C-compatible void array
example: [[1.0, 2.0], [3.0, 4.0]]
"""
_fields_ = [("data", c_void_p),
("len", c_size_t)]
@classmethod
def from_param(cls, seq):
""" Allow implicit conversions """
return seq if isinstance(seq, cls) else cls(seq)
def __init__(self, seq, data_type = c_double):
arr = ((c_double * 2) * len(seq))()
for i, member in enumerate(seq):
arr[i][0] = member[0]
arr[i][1] = member[1]
self.data = cast(arr, c_void_p)
self.len = len(seq)
argtype
和 restype
定义:
encode_coordinates = lib.encode_coordinates_ffi
encode_coordinates.argtypes = (_FFIArray,)
encode_coordinates.restype = c_void_p
encode_coordinates.errcheck = char_array_to_string
drop_cstring = lib.drop_cstring
drop_cstring.argtypes = (c_char_p,)
drop_cstring.restype = None
我倾向于认为它不是Rust函数,因为dylib崩溃会导致段错(并且FFI测试在Rust端传递) . 在调用FFI函数之后,我还可以继续使用Python中的其他操作 - 当进程退出时会发生malloc错误 .
2 回答
感谢J.J. Hakala's answer的努力,我能够在纯Rust中生成MCVE:
这打印:
主要问题在于:
我们来看看documentation for Vec::from_raw_parts:
但是,原始代码显示为 violates 第一个点 - 指针由
malloc
分配 .为什么这会发挥作用?当您调用
Vec::from_raw_parts
时,它将获得指针的所有权 . 当Vec
超出范围时,指向的内存为 deallocated . 这意味着您尝试多次释放该指针 .因为函数的安全性是由传入的内容决定的,entire function should be marked unsafe . 在这种情况下,这将违反特征的界面,因此您需要将其移动到其他位置 .
更理智的是,您可以将
Array
转换为切片 . 这仍然是不安全的,因为它取决于传入的指针,但它不拥有底层指针 . 然后,您可以将切片变为Vec
,分配新内存并复制内容 .由于您可以控制
encode_coordinates
,因此您还应该更改其签名 . 在99.99%的情况下,&Vec<T>
是无用的,实际上可能效率较低:它需要两个指针解引用而不是一个 . 相反,接受&[T]
. 这允许传递更广泛的类型,包括数组和Vec
.我认为代码的Rust端假定数据的所有权并尝试在进程退出时释放数据,因此不应该责怪Python代码 . 作为证明,以下调用
encode_coordinates_ffi
和drop_cstring
的C代码也会导致分段错误 .valgrind -v
提供以下信息如果省略
free(data.data)
,则程序完成而没有分段错误,并且valgrind没有发现任何内存泄漏 .我会尝试实现接口,使其对应
其中
dst
将用于编码字符串(长度限制n
,基于坐标数npoints
的一些近似值),因此调用者不需要释放Rust字符串 .