有没有更惯用的方法来保持可选参数字符串被释放?

Rya*_*729 3 c ffi command-line-arguments rust

我想在Rust程序中获取命令行参数并将它们传递给C函数.但是,这些参数是可选的,如果没有提供参数,程序应该表现不同.我已经阅读了文档,CString::as_ptr但是我曾希望保留一个Option包含参数的局部变量(如果存在)将使其String不被释放,如下例所示.

这个Rust代码:

extern crate libc;

use std::ffi::CString;

extern "C" {
    fn print_in_c(opt_class: *const libc::c_char) -> libc::c_int;
}

fn main() {
    let mut args = std::env::args();
    //skip execuatble name
    args.next();

    let possible_arg = args.next();

    println!("{:?}", possible_arg);

    let arg_ptr = match possible_arg {
        Some(arg) => CString::new(arg).unwrap().as_ptr(),

        None => std::ptr::null(),
    };

    unsafe {
        print_in_c(arg_ptr);
    };
}
Run Code Online (Sandbox Code Playgroud)

与此C代码一起:

#include <stdio.h>
int
print_in_c(const char *bar)
{
  puts("C:");
  puts(bar);

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

但这没效果.传递参数"foo"时,代码打印出以下内容:

Some("foo")
C:
Run Code Online (Sandbox Code Playgroud)

后跟一个空白行.

如果我将Rust代码更改为以下内容,我得到了打印正确文本的程序:

extern crate libc;

use std::ffi::CString;

extern "C" {
    fn print_in_c(opt_class: *const libc::c_char) -> libc::c_int;
}

fn main() {
    let mut args = std::env::args();
    //skip execuatble name
    args.next();

    let possible_arg = args.next();

    println!("{:?}", possible_arg);

    let mut might_be_necessary = CString::new("").unwrap();

    let arg_ptr = match possible_arg {
        Some(arg) => {
            might_be_necessary = CString::new(arg).unwrap();
            might_be_necessary.as_ptr()
        }

        None => std::ptr::null(),
    };

    unsafe {
        print_in_c(arg_ptr);
    };
}
Run Code Online (Sandbox Code Playgroud)

运行时,会打印出来

Some("foo")
C:
foo
Run Code Online (Sandbox Code Playgroud)

正如所料.

此方法在技术上有效,但扩展到多个参数并导致编译器警告很难:

warning: value assigned to `might_be_necessary` is never read
  --> src/main.rs:19:9
   |
19 |     let mut might_be_necessary = CString::new("").unwrap();
   |         ^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: #[warn(unused_assignments)] on by default
Run Code Online (Sandbox Code Playgroud)

有一个更好的方法吗?

use*_*342 8

问题是你的代码正在创建一个临时的,CString但只是一个指针.实际CString被删除,而悬空指针被传递给C函数.要了解发生了什么,将模式匹配扩展为更详细的形式非常有用:

let arg_ptr = match possible_arg {
    Some(arg) => {
        let tmp = CString::new(arg).unwrap();
        tmp.as_ptr()
    } // <-- tmp gets destructed here, arg_ptr is dangling
    None => std::ptr::null(),
};
Run Code Online (Sandbox Code Playgroud)

Safe Rust通过仅通过引用支持指针间接来防止悬空指针,引用的生命周期由编译器仔细跟踪.在编译时会自动拒绝使用超过对象的引用.但是您使用原始指针和unsafe阻止这些检查发生的块,因此您需要手动确保适当的生命周期.实际上,第二个片段通过创建一个局部变量来解决问题,该变量存储的CString时间足够长,使其值超过指针.

延长的寿命是以额外的局部变量为代价的.但幸运的是可以避免 - 因为你已经拥有一个保存指针的局部变量,你可以修改它来存储实际值CString,并仅在实际需要时提取指针:

let arg_cstring = possible_arg.map(|arg| CString::new(arg).unwrap());
unsafe {
    print_in_c(arg_cstring.as_ref()
               .map(|cs| cs.as_ptr())
               .unwrap_or(std::ptr::null()));
}
Run Code Online (Sandbox Code Playgroud)

这里有几点需要注意:

  • arg_cstring持有一个Option<CString>,确保CString存储能够比传递给C函数的指针更长;
  • Option::as_ref()用于防止arg_cstring感动map,这将再次释放它实际上是用指针之前;
  • Option::map()当你想表达"用Optionif 做什么Some,否则只是留下它None"时,它被用作模式匹配的替代品.
  • x.as_ref().map(|x| x.as_ptr().unwrap_or(null())如果在程序中多次使用该模式,则该模式可以并且可能应该移动到实用程序函数中.请注意该功能需要参考Option以避免移动.