Dereferencing a raw pointer yields varying results


#1

While trying to convert a [&str] or Vec<&str> to a list of C-String-pointers to be passed by FFI, I received some odd results. When I broke down the problem I came up with the following code:

let raw_ptr = &'@' as *const char as *const i8;
println!("Dereferenced: {}", unsafe { *raw_ptr });
println!("Dereferenced: {}", unsafe { *raw_ptr });
println!("Dereferenced: {}", unsafe { *raw_ptr });
println!("Dereferenced: {}", unsafe { *raw_ptr });

which correctly results in:

Dereferenced: 64
Dereferenced: 64
Dereferenced: 64
Dereferenced: 64

But running the following code:

let raw_ptr: *const i8 = std::ffi::CString::new("A").unwrap().as_ptr();
println!("Dereferenced: {}", unsafe { *raw_ptr });
println!("Dereferenced: {}", unsafe { *raw_ptr });
println!("Dereferenced: {}", unsafe { *raw_ptr });
println!("Dereferenced: {}", unsafe { *raw_ptr });

results in

Dereferenced: 65
Dereferenced: 40
Dereferenced: 40
Dereferenced: 40

The first line always outputs the correct result and each subsequent access seems to point somewhere wrong. The exact value depends on the programs history. I also tried to artificially expand the raw_ptr's lifetime by re-using it at the end of the code, but to no avail.

One more test:

let raw_ptr: *const i8 = std::ffi::CString::new("A").unwrap().as_ptr();
unsafe { 
    println!("Dereferenced: {}", *raw_ptr);
    println!("Dereferenced: {}", *raw_ptr);
    println!("Dereferenced: {}", *raw_ptr);
    println!("Dereferenced: {}", *raw_ptr);
}

results in

Dereferenced: -88
Dereferenced: -88
Dereferenced: -88
Dereferenced: -88

… which makes me think my approach is completely broken.

In the original code I tried to convert multiple strings using CString, but only some pointers came out as expected.

Can someone please explain to me, what’s wrong with this code?


#2

as_ptr will get you a pointer, but the original CString is still a temporary.

You can either keep the CString somewhere, or use into_raw to relinquish ownership (and use CString::from_raw later to clean it up, if you want to avoid leaks).


#3

The CString returned by CString::new is an owned string. That means that it will only be valid as long as its binding is. However, you are not assigning it to any binding; it is just a temporary, which means it just lives as long as the statement it is used in. After you unwrap it, you need to assign it to a variable, and then it will live as long as the block in which that variable is valid, or longer if you move it out by returning it or putting it in some longer-lived data structure.

let str = std::ffi::CString::new("A").unwrap();
let raw_ptr = str.as_ptr();
println!("Dereferenced: {}", unsafe { *raw_ptr });
println!("Dereferenced: {}", unsafe { *raw_ptr });
println!("Dereferenced: {}", unsafe { *raw_ptr });
println!("Dereferenced: {}", unsafe { *raw_ptr });

Or, as @eddyb says, you can use into_raw to have the raw pointer take ownership, but that then gives you the responsibility to convert it back and free it later on.

 let raw_ptr = std::ffi::CString::new("A").unwrap().into_raw();
 println!("Dereferenced: {}", unsafe { *raw_ptr });
 println!("Dereferenced: {}", unsafe { *raw_ptr });
 println!("Dereferenced: {}", unsafe { *raw_ptr });
 println!("Dereferenced: {}", unsafe { *raw_ptr });
 let _ = unsafe { std::ffi::CString::from_raw(raw_ptr) };

#4

Hey thanks! So it’s all about the lifetime of my CString, which provides the pointer with some flesh?

I just saw the subtle difference between my code an the example given in
https://doc.rust-lang.org/std/ffi/struct.CString.html#examples

Now I understand the purpose of into_raw. I’ll try out which solution fits my needs best.

Thank @lambda for providing the helpful example!