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

Jia*_*Liu 3 unsafe libc execvp rust

我一直在尝试在Rust中编写一个直接链接到libc库的shell .我用a 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),
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

E_n*_*ate 8

您正在创建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();
Run Code Online (Sandbox Code Playgroud)

游乐场.


Mat*_* M. 5

我建议您再次阅读该文档CString::as_ptr:

警告

您有责任确保底层内存不会过早释放。例如,ptr在unsafe块内使用以下代码将导致未定义的行为:

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

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

发生这种情况是因为所返回的指针as_ptr不包含任何生存期信息,并且CString在对CString::new("Hello").expect("CString::new failed").as_ptr()表达式求值后立即将其释放。要解决此问题,请将绑定CString到本地变量:

# #![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;
}
Run Code Online (Sandbox Code Playgroud)

这样,CStringin 的生存期就hello包含了ptr和unsafe块的生存期。

您正在完全按照文档所说做。