CString::new().unwrap().as_ptr() gives empty *cons

2020-02-02 02:37发布

问题:

I have a C function that expects *const std::os::raw::c_char and I have done the following in Rust:

use std::os::raw::c_char;
use std::ffi::{CString, CStr};
extern crate libc;

fn main() {
    let _test_str: *const c_char = CString::new("Hello World").unwrap().as_ptr();
    let fmt: *const c_char = CString::new("%s\n").unwrap().as_ptr();
    unsafe { libc::printf(fmt, _test_str); }

    unsafe {
        let slice = CStr::from_ptr(_test_str);
        println!("string buffer size without nul terminator: {}", slice.to_bytes().len());
    }
}

However, I cannot get _test_str print out and the output of the above program is simply

string buffer size without nul terminator: 0

If I pass the _test_str into some C function and see it is an empty string. What did I do wrong?

回答1:

You are creating a CString in the same statement as creating a pointer to it. The CString is owned but not bound to a variable so it only lives as long as the enclosing statement, causing the pointer to become invalid. This is specifically warned about by the documentation for as_ptr:

For example, the following code will cause undefined behavior when ptr is used inside the unsafe block:

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.

You can fix the problem by introducing variables which will live for the entire function, and then create pointers to those variables:

fn main() {
    let owned_test = CString::new("Hello World").unwrap();
    let _test_str: *const c_char = owned_test.as_ptr();
    let owned_fmt = CString::new("%s\n").unwrap();
    let fmt: *const c_char = owned_fmt.as_ptr();

    unsafe {
        libc::printf(fmt, _test_str);
    }

    unsafe {
        let slice = CStr::from_ptr(_test_str);
        println!(
            "string buffer size without nul terminator: {}",
            slice.to_bytes().len()
        );
    }

    // owned_fmt is dropped here, making fmt invalid
    // owned_test is dropped here, making _test_str invalid
}

If you are working with raw pointers, you need to be extra careful that they are always pointing at live data. Introducing a variable is the best way to control exactly how long that data lives - it will live from the initialization of the variable to the moment the variable goes out of scope.