You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 00:48:10