将 Vec<String> 从 Rust 传递到 C 中的 char**

Passing Vec<String> from Rust to char** in C

我一直在尝试用 Rust 编写一个直接链接到 libc 库的 shell。我已经使用 Vec<String> 来保存要传递给 execvp() 的参数,但似乎我转换为 char ** 没有成功。执行后,所有参数都变成了空字符串。

这是涉及的代码。

fn safe_execvp(path: String, argv: Vec<String>) -> Result<(), i32> {
    unsafe {
        let c_path = CString::new(path.as_str()).unwrap();
        let mut c_argv_vec = Vec::new();
        for arg in &argv {
            let c_arg = CString::new(arg.as_str()).unwrap().as_ptr();
            c_argv_vec.push(c_arg);
        }
        c_argv_vec.push(std::ptr::null());
        match execvp(c_file.as_ptr(), c_argv_vec.as_ptr()) {
            num => Err(num),
        }
    }
}

execvp是定义为fn execvp(file: *const i8, argv: *const*const i8) -> i32;.

的C库函数

我不确定我做错了什么。是因为参数的内存在调用execvp()之前被释放了吗?

我建议你再读一遍the documentation for CString::as_ptr

WARNING

It is your responsibility to make sure that the underlying memory is not freed too early. For example, the following code will cause undefined behavior when ptr is used inside the unsafe block:

# #![allow(unused_must_use)]
use std::ffi::{CString};

let ptr = CString::new("Hello").expect("CString::new failed").as_ptr();
unsafe {
    // `ptr` is dangling
    *ptr;
}

This happens because the pointer returned by as_ptr does not carry any lifetime information and the CString is deallocated immediately after the CString::new("Hello").expect("CString::new failed").as_ptr() expression is evaluated. To fix the problem, bind the CString to a local variable:

# #![allow(unused_must_use)]
use std::ffi::{CString};

let hello = CString::new("Hello").expect("CString::new failed");
let ptr = hello.as_ptr();
unsafe {
    // `ptr` is valid because `hello` is in scope
    *ptr;
}

This way, the lifetime of the CString in hello encompasses the lifetime of ptr and the unsafe block.

您所做的正是文档中禁止做的事情。

您正在创建 CString 个实例并立即获取指针。因此,正如您猜到的那样,这个字符串的所有权被过早地删除了。这类似于返回对本地实例的引用,只是由于指针不保留生命周期信息,因此这种情况不会触发编译错误。

您的问题的解决方案是在函数范围内保留拥有的 C 样式字符串,并分别生成指向相同内容的指针的指针。

let cstr_argv: Vec<_> = argv.iter()
        .map(|arg| CString::new(arg.as_str()).unwrap())
        .collect();

let mut p_argv: Vec<_> = cstr_argv.iter() // do NOT into_iter()
        .map(|arg| arg.as_ptr())
        .collect();

p_argv.push(std::ptr::null());

let p: *const *const c_char = p_argv.as_ptr();

Playground.

另请参阅: