传递 int 数组时如何修复分段错误?

How to fix a segmentation fault when passing an array of int?

我有一个 Rust (1.2) 库,我想使用 Rust 的 FFI 从 Python (3.4) 中使用它的函数。我在 OSX 10.10 Yosemite.

我的几乎所有函数都将可变切片引用作为输入:

pub fn myfunction<T>(array: &mut [T]) { ... }

然后我将此函数暴露在 Rust 之外使用:

#[no_mangle]
pub extern fn ffi_myfunction(array_pointer: *const libc::int8_t, n: libc::size_t) {
    assert!(!array_pointer.is_null());
    assert!(n != 0);
    let mut to_sort = unsafe {
        slice::from_raw_parts_mut(array_pointer as *mut i8, n as usize)
    };
    myfunction(&mut to_sort);
}

这很好用:使用 python 的 ctypes 模块我可以用一个 numpy 数组调用 ffi_myfunction()

#!/usr/bin/env python3

import ctypes
import numpy as np

rustlib = ctypes.CDLL("target/debug/libmylib.dylib")

array = np.arange(5, dtype=np.int8)

rustlib.ffi_myfunction(ctypes.c_void_p(array.ctypes.data), len(array))

我也有 libc::int16_tlibc::int32_tlibc::int64_t 的 Rust 实现,我可以用 np.int16np.int32 和 [=21 调用它们=].

我想从 Python 调用第二组 Rust 函数。这些函数略有不同,因为它们采用对向量(而不是切片)的可变引用:

pub fn myotherfunction<T>(array: &mut Vec<T>) { ... }

然后我这样创建我的包装器:

#[no_mangle]
pub extern "C" fn ffi_myotherfunction(array_pointer: *const libc::int8_t, n: libc::size_t) {
    assert!(!array_pointer.is_null());
    assert!(n != 0);
    let mut to_sort = unsafe {
        Vec::from_raw_parts(array_pointer as *mut i8, n as usize, n as usize)
    };
    myotherfunction(&mut to_sort);
}

不幸的是,我在从 Python 调用 ffi_myotherfunction() 时出现分段错误。

经过一番调查,我可以说如下:

  1. myotherfunction()ffi_myotherfunction() 中的任意位置添加 println!() 宏可使函数正常执行。输出符合预期。
  2. rust 库对于使用的任何整数大小都会出现段错误(尝试了 8、16、32 和 64 位整数)。
  3. 段错误似乎不是来自 myotherfunction(),而是来自对 Vec::from_raw_parts() 的调用。例如,我可以注释掉 ffi_myotherfunction()myotherfunction() 的调用,只留下不安全的块,段错误仍然发生。

所以slice::from_raw_parts_mut()Vec::from_raw_parts()好像有区别。

但我不明白是什么导致了该段错误。我错过了什么吗?难道我做错了什么? numpy 存储数据的方式会不会有问题?或者可能是关于生命周期、借用或任何其他我没有得到的生锈概念?

谢谢!

您应该只将 Vec::from_raw_parts 用于已在 Rust 代码中使用 Rust 分配器分配的数据。其他任何东西都是不安全的。

我希望 Python 使用 malloc,但 Rust 使用 jemalloc。如果指示 jemalloc 释放一个不是由 jemalloc 分配的地址,我相信它会崩溃。因此,如果一个 vector 被释放(即它超出范围,它的析构函数是 运行)或者如果它需要重新分配(例如,如果你将一个元素推到它上面),你将遇到崩溃。

第一个问题是 Vec 有它的析构函数 运行;完成后可以通过调用 std::mem::forget(to_sort) 来修改。另一个问题,即任何重新分配都会崩溃,这要危险得多;你基本上已经做到了你不能安全地可变地访问你的向量,并且必须 非常 对你做的任何事情保持谨慎。实际上,您应该假设 Vec 上的 一切 都会发生崩溃。你应该使用 &mut [T] 来代替,如果你想要的可以用它来完成的话。