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

7
我一直在尝试用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是C语言库函数,定义如下:fn execvp(file: *const i8, argv: *const*const i8) -> i32;

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

2个回答

12

你正在创建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

另请参阅:CString::new().unwrap().as_ptr()返回空的*const c_char


谢谢!我想我应该把CString放在一个向量中。 - Jianzhong Liu

6

我建议您再次阅读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.

您正在执行文档中明确禁止的操作。


谢谢!我读了那部分,但没有意识到我犯了它警告不要犯的错误。 - Jianzhong Liu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接