c - 通过 bindgen 从 Rust 指向 C 的指针:第一个元素始终为零
问题描述
我使用 bindgen 为我的 Rust 代码生成 C 接口。我想返回一个包含Option<Vec<f64>>
from Rust to的结构C
。在Rust
我创建了以下结构:
#[repr(C)]
pub struct mariettaSolverStatus {
lagrange: *const c_double
}
其中 bindgen 转换为以下 C 结构:
/* Auto-generated structure */
typedef struct {
const double *lagrange;
} mariettaSolverStatus;
Rust 中对应的结构是
pub struct AlmOptimizerStatus {
lagrange_multipliers: Option<Vec<f64>>,
}
impl AlmOptimizerStatus {
pub fn lagrange_multipliers(&self) -> &Option<Vec<f64>> {
&self.lagrange_multipliers
}
}
这个想法是将AlmOptimizerStatus
(在 Rust 中)映射到mariettaSolverStatus
(在 C 中)。当lagrange_multipliers
is时None
,一个空指针将被分配给 C 中的指针。
现在在 Rust 中,我有以下功能:
#[no_mangle]
pub extern "C" fn marietta_solve(
instance: *mut mariettaCache,
u: *mut c_double,
params: *const c_double
) -> mariettaSolverStatus {
/* obtain an instance of `AlmOptimizerStatus`, which contains
* an instance of `&Option<Vec<f64>>`
*/
let status = solve(params, &mut instance.cache, u, 0, 0);
/* At this point, if we print status.langrange_multipliers() we get
*
* Some([-14.079295698854809,
* 12.321753192707693,
* 2.5355683425384417
* ])
*
*/
/* return an instance of `mariettaSolverStatus` */
mariettaSolverStatus {
lagrange: match &status.lagrange_multipliers() {
/* cast status.lagrange_multipliers() as a `*const c_double`,
* i.e., get a constant pointer to the data
*/
Some(y) => {y.as_ptr() as *const c_double},
/* return NULL, otherwise */
None => {0 as *const c_double},
}
}
}
Bindgen 生成一个 C 头文件和库文件,允许我们在 C 中调用 Rust 函数。到目前为止,我应该说我没有收到来自 Rust 的警告。
但是,当我从 C 调用上述函数时,使用自动生成的 C 接口,的第一个元素mariettaSolverStatus.lagrange
always 0
,而所有后续元素都正确存储。
这是我的 C 代码:
#include <stdio.h>
#include "marietta_bindings.h"
int main() {
int i;
double p[MARIETTA_NUM_PARAMETERS] = {2.0, 10.0}; /* parameters */
double u[MARIETTA_NUM_DECISION_VARIABLES] = {0}; /* initial guess */
double init_penalty = 10.0;
double y[MARIETTA_N1] = {0.0};
/* obtain cache */
mariettaCache *cache = marietta_new();
/* solve */
mariettaSolverStatus status = marietta_solve(cache, u, p, y, &init_penalty);
/* prints:
* y[0] = 0 <------- WRONG!
* y[1] = 12.3218
* y[2] = 2.5356
*/
for (i = 0; i < MARIETTA_N1; ++i) {
printf("y[%d] = %g\n", i, status.lagrange[i]);
}
/* free memory */
marietta_free(cache);
return 0;
}
我猜想不知何故,某个地方,一些指针超出了范围。
解决方案
我很确定问题在于您的marietta_solve
. 让我们逐行浏览
let status = solve(params, &mut instance.cache, u, 0, 0);
您已经分配了 anAlmOptimizerStatus
及其所有内部成员。到这里为止,一切都是洁净的(假设solve
不做傻事)
mariettaSolverStatus {
lagrange: match &status.lagrange_multipliers() {
/* cast status.lagrange_multipliers() as a `*const c_double`,
* i.e., get a constant pointer to the data
*/
Some(y) => {y.as_ptr() as *const c_double},
/* return NULL, otherwise */
None => {0 as *const c_double},
}
}
然后,您决定返回一个指向即将超出范围并被删除的原始指针( )。在里面,你有你要返回的指针。struct
status
Option<Vec<f64>>
结果,这会导致 UB - 您的向量不再在内存中,但您有一个指向它的原始指针。而且,由于 rust 在使用原始指针时不会保护您免受这种情况的影响,因此不会出现错误。在您分配其他东西的那一刻(就像您在定义 时所做的那样int i
),您可能会覆盖您之前使用(和释放)的一些内存。
您可以通过这个游乐场示例说服自己相信这一点,其中我已将原始指针替换为引用以触发借用检查器。
为了摆脱这个问题,你需要强制让 Rust 忘记向量的存在,就像这样(游乐场):
impl AlmOptimizerStatus {
pub fn lagrange_multipliers(self) -> Vec<f64> {
self.lagrange_multipliers.unwrap_or(vec![])
}
}
fn test() -> *const c_double {
let status = solve();
let output = status.lagrange_multipliers();
let ptr = output.as_ptr();
std::mem::forget(output);
ptr
}
注意变化:
lagrange_multipliers()
现在解构你的struct
并采用内部向量。如果您不想要这个,则需要制作一份副本。由于这不是问题的目的,因此我进行了解构以降低代码std::mem::forget
忘记了一个 rust 对象,允许它超出范围而不被释放。这就是您通常通过 FFI 边界传递对象的方式,第二个选项是通过 或其他方式分配MaybeUninit
内存std::ptr
。
显而易见的问题:在不处理我们在 C 端(通过free
)或 rust 端(通过重新组合Vec
然后正确删除它)创建的内存泄漏的情况下这样做显然会泄漏内存
推荐阅读
- python - 如何在两个 GPU 上运行两个不同的神经网络?
- asp.net-mvc - ASP.Net Core 3 排序外键下拉选择列表
- python - 为什么元组在 python 中切片`O(n)`?
- r - Shinny:无法使用传单从输入文件打开 shapefile
- javascript - 由 azure 媒体播放器托管的视频网址不接受媒体片段
- javascript - 将 const 更改为数组
- php - 使用 PHP 抓取包含 jpg 和 webp 的 img src 的网站
- angular - 如何用组件替换容器?
- javascript - If Else at append js
- c++ - 无法使用 RenderTexture 创建精灵