Передача Vec‹String› из Rust в char** в C

Я пытался написать оболочку на Rust, которая напрямую связана с библиотекой libc. Я использовал 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()?


person Jianzhong Liu    schedule 10.03.2017    source источник


Ответы (2)


Вы создаете экземпляры 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();

Игровая площадка.

См. также: CString::new().unwrap().as_ptr() дает пустой *const c_char

person E_net4 the curator    schedule 10.03.2017
comment
Спасибо! Думаю, мне следовало сохранить CString в векторе. - person Jianzhong Liu; 10.03.2017

Я предлагаю вам прочитать документацию для 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;
}

Это происходит потому, что указатель, возвращаемый 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;
}

Таким образом, время жизни CString в hello охватывает время жизни ptr и блока unsafe.

Вы делаете именно то, что в документации сказано не делать.

person Matthieu M.    schedule 10.03.2017
comment
Спасибо! Я прочитал эту часть, но не понял, что совершил ошибку, о которой предупреждали. - person Jianzhong Liu; 10.03.2017