3

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?

E_net4
  • 27,810
  • 13
  • 101
  • 139
xxks-kkk
  • 2,336
  • 3
  • 28
  • 48
  • 2
    Possible duplicate of [Conversion between a Rust str and ffi::CString and back again partially corrupts the string](https://stackoverflow.com/questions/48235267/conversion-between-a-rust-str-and-fficstring-and-back-again-partially-corrupts) – trent Sep 05 '18 at 01:57
  • 1
    @trentcl Didn't expect to see the warning if I keep staring at the introduction section of the page instead of the `as_ptr` function subsection explanation. My bad but "recklessly" is just too much. Thanks anyway for pointing out the reference. Also, the question you pointed out is much more lengthy and hard to grasp the key point. – xxks-kkk Sep 05 '18 at 02:23
  • 1
    I'm sorry for not linking directly to the relevant section, but I shouldn't have had to link it *at all*; it's your responsibility to guarantee any `unsafe` code you write and that includes reading the documentation for functions that return raw pointers, before dereferencing them. As for the dupe, I agree, that particular one is a bit long-winded. Actually [How to stop memory leaks when using `as_ptr()`?](https://stackoverflow.com/questions/31083223/how-to-stop-memory-leaks-when-using-as-ptr?noredirect=1&lq=1) is probably better. However, I only get one close vote, so I can't change it now. – trent Sep 05 '18 at 11:24

1 Answers1

8

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Peter Hall
  • 53,120
  • 14
  • 139
  • 204