Rust与C交互FFI中c_void指针异常行为排查及正确实现
关于Rust FFI与C交互的内存问题及正确实现
问题背景
我是Rust新手,正在实现Rust结构体与C代码交互的Foreign Function Interface(FFI),目标是将Rust代码编译为C动态库,需要实现以下功能:
- 一个存储单个浮点数的简单结构体;
- 用浮点数初始化结构体并返回对应
void*指针的函数; - 通过指针读取结构体中浮点数的函数。
初始代码及问题
初始尝试的代码如下:
use core::ffi::c_void; #[repr(C)] pub struct number_container{ contained_number:f64, } #[no_mangle] pub extern "C" fn init_number_container(number:f64) -> *mut c_void { let mut num_cont:number_container = number_container {contained_number:number}; let ptr: *mut number_container = &mut num_cont; let voidptr = ptr as *mut c_void; voidptr } #[no_mangle] pub extern "C" fn get_number_from_container(num_cont: *mut c_void) -> f64{ // UNCOMMENT THIS LINE --> num_cont2.contained_number becomes invalid // println!("{}", format!("voidptr-adress {:p}", num_cont) ); let num_cont2: &mut number_container = unsafe { &mut *(num_cont as *mut number_container) }; return num_cont2.contained_number } fn main() { println!("contained number: {}", get_number_from_container(init_number_container(12.83454)) ); }
现象
编译运行时能得到预期结果:
contained number: 12.83454
但取消注释打印指针地址的println!语句后,返回值变成了无效的垃圾值:
contained number: 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001134413904036
问题原因
核心错误是返回了栈上变量的指针:num_cont是init_number_container函数内的栈变量,函数执行完毕后栈内存会被系统回收或后续操作覆盖。未打印时只是侥幸没被覆盖,打印操作会占用栈空间,直接覆盖了原结构体的内存,导致读取到垃圾值。
正确实现方案
需要在堆上分配结构体内存,用Rust的Box管理内存,通过Box::into_raw将堆内存指针暴露给C,同时必须提供释放函数避免内存泄漏。
改进后的Rust代码
#[repr(C)] pub struct number_container{ contained_number:f64, } #[no_mangle] pub extern "C" fn init_number_container(number:f64) -> *mut number_container { // 在堆上分配结构体,转为裸指针返回给C Box::into_raw(Box::new(number_container { contained_number: number })) } #[no_mangle] pub extern "C" fn free_number_container(num_cont:*mut number_container) { unsafe { assert!(!num_cont.is_null()); // 将裸指针转回Box,自动释放内存 drop(Box::from_raw(num_cont)); } } #[no_mangle] pub extern "C" fn get_number_from_container(num_cont: *mut number_container) -> f64{ unsafe { (*num_cont).contained_number } } fn main() { let num_cont: *mut number_container = init_number_container(12.83454); println!("pointer-adress {:p}", num_cont ); println!("contained number: {}", get_number_from_container(num_cont) ); free_number_container(num_cont); }
对应的C头文件
#include <stdarg.h> #include <stdbool.h> #include <stdint.h> #include <stdlib.h> void *init_number_container(double number); double get_number_from_container(void *num_cont); void free_number_container(void* num_cont);
关键注意事项
- 必须用
#[repr(C)]标记结构体,确保其内存布局与C语言兼容; - 用
Box::into_raw将堆分配的结构体转为裸指针,交给C使用; - 必须提供
free_number_container函数,让C可以调用它释放Rust分配的内存,避免内存泄漏; - 可以直接返回
number_container*指针给C(Rust和C的ABI兼容),也可以转为void*,两种方式都安全。
内容的提问来源于stack exchange,提问作者user578943
相关产品推荐
相关产品推荐

