Rust FFI编程中的内存分配


熟悉rust的朋友,应该都知道rust语言本身是没用垃圾回收的概念的。为了在编译期间就能合理的规划和使用内存,rust的变量都有自己的生命周期,在发生借用和转移之后就会被释放。因此rust并不需要java(分代和并发)和go(三色标记和并行)等这样的垃圾集中回收机制,或者像c/c++一样的主动分配释放的机制。
因此在rust FFI编程中就会遇到一个有意思的问题,如何像C一样将一块分配好的内存传递给另一个函数调用方。让rust的变量所有者在离开了这个作用域后这个值不被清理,而是像c一样来主动释放?
那么通过rust来调用libc的malloc和free将无疑是一个很好的解决方案。让rust像胶水层一样粘连在c程序和rust(libc)之间。这样就不用担心rust的主动清理问题了。

一. 关于Rust FFI编程

Rust 的 FFI(Foreign Function Interface)功能,允许您使用 Rust 编写 C 兼容的静态和动态链接库,使其可以和C或者其他语言的应用程序进行交互。
从 Rust 的角度来看,FFI 是通过在 Rust 代码中定义 C 兼容的接口来实现的,而从 C 应用程序的角度来看,则需要使用与 Rust 兼容的头文件和库文件。

1. rust FFI编程示例

接下来我将给出一个简单的例子,例子中rust实现了一个求和功能的函数,而这个函数将被编译成C语言能够调用的动态链接库来供C程序调用。
实现Rust 的 FFI能力,主要有以下步骤:

  1. 使用 extern 和 #[no_mangle] 标记 Rust 函数(lib.rs中),以便可以从其他语言调用该函数:
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}
  1. 在 Cargo.toml 文件中,设置 crate-type 选项为 cdylib:
[lib]
name = "add"
crate-type = ["cdylib"]

之后通过 cargo build 命令来编译我们的rust程序,这样就能够通过C语言使用动态链接库来调用我的add方法了。

  1. 在 C 代码中包含 Rust FFI 公开的头文件,并调用 Rust 函数。
#include "add.h"

int main() {
    int sum = add(2, 3);
    printf("sum = %d\n", sum);
    return 0;
}
  1. 编译 C 代码并链接 Rust FFI 动态链接库
gcc main.c -o main -L/target/release/ -l add

二. rust FFI中内存分配

下面让我来看一看,如何在rust中实现内存分配,并将内存地址传递给C程序,让C程序来调用,并释放。
示例程序中,将通过rust来规划一块pools的结构体数组空间,并将结构体数组的地址传递给c程序,c程序在使用完这块空间后调用rust提供的函数指针将其释放(这里其实用c来释放也行,但是本着谁的孩子谁来带的原则,这里还是提供了一个内存释放函数,让rust来分配和释放这块空间)。
以下是示例程序的目录结构:

.
├── Cargo.lock
├── Cargo.toml
├── include
│   └── test.h
├── src
│   └── lib.rs
└── test.c
  1. 动态链接口的头文件 include/test.h (这个文件更像是C和rust在ffi之间的协议一样)
#ifndef TEST_H
#define TEST_H
//Pool 结构体
typedef struct {
    int id;
    char name[20];
} Pool;
// 此方法,接受三个内存地址:pools,分配好的结构体数组空间的指针地址,len:结构体数组的长度, mem_free: 内存释放函数指针地址
void create_pools(Pool **pools,unsigned int *len,void (**mem_free)(void **));
#endif /*TEST*/
  1. 导入rust需要使用的库,并配置Cargo.toml
[package]
name = "demo"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]

[dependencies]
libc = "0.2.0"
  1. 在明确头文件后,我们就可以根据头文件来实现我门的rust ffi程序了。src/lib.rs
use std::os::raw;
use libc::{free, malloc};
use std::mem::size_of;
// Pool 结构体
pub struct Pool {
    pub id: i32,
    pub name: [raw::c_char; 20usize],
}

pub extern "C" fn mem_free(ptr: *mut *mut raw::c_void) {
    println!("I‘m free func");
    if ptr.is_null() {
        println!("it is null, nothing to free");
        return;
    }
    unsafe { free(*ptr) } //调用libc库的free方法,释放内存空间
}

#[no_mangle]
pub extern "C" fn create_pools(
    pools: *mut *mut Pool,
    len: *mut raw::c_uint,
    mem_free: *mut ::std::option::Option<unsafe extern "C" fn(arg1: *mut *mut raw::c_void)>,
) {
    // 分配内存的大小
    let size = 3 * size_of::<Pool>();

    // 使用 malloc 分配内存
    unsafe {
        let ptr = malloc(size);

        // 向分配的内存写入数据
        let array = std::slice::from_raw_parts_mut(ptr as *mut Pool, 3) ;
        for i in 0..3 {
            let name_str = format!("demo_{}", i);
            let c_string = std::ffi::CString::new(name_str).unwrap();
            let names = c_string.as_bytes_with_nul();
            let mut name_buffer: [raw::c_char; 20] = [0; 20];
            for (i, byte) in names.iter().enumerate() {
                name_buffer[i] = *byte as raw::c_char;
            }
            
            array[i] = Pool {
                id: i as i32,
                name: name_buffer,
            };
        }

        // 返回指向分配的内存的指针,pools的长度len,和释放内存函数指针回填。
        *pools = ptr as *mut Pool;
        *len = 3;
        *mem_free = Some(mem_free);
    };
}
  1. 在C程序的测试示例:test.C
#include "include/test.h"
#include <stdio.h>

int main(void){
    Pool *pools = NULL;
    unsigned int len = 0;
    void (*free_func)(void **ptr) = NULL;
    //调用rust的内存分配函数
    create_pools(&pools,&len,&free_func);
    int i;
    for(i=0;i<3;i++){
        printf("name:%s  id:%d \n",pools[i].name,pools[i].id);              
    }
    //调用rust的内存释放函数
    free_func(&pools);
    printf("demo_0: name:%s  id:%d \n",pools[0].name,pools[0].id);  
}
  1. 测试验证
[root@local demo]# cargo build
[root@local demo]# gcc -Wall -g -O0 test.c -I. -L target/debug/ -l demo
[root@local demo]# LD_LIBRARY_PATH=target/debug ./a.out
name:demo_0  id:0 
name:demo_1  id:1 
name:demo_2  id:2 
I‘m free func
demo_0: name:  id:0 

cargo build 后将在 ./target/debug 目录下生成一个libdemo.so动态链接库。
gcc -Wall -g -O0 test.c -I. -L target/debug/ -l demo 链接编译

从示例的输出中可以看到,在释放完成后访问对应的pools成员变量将得到空值。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值