Reputation: 2945
Please do not mark the question as duplicate of casting *mut u8 to &[u8] without std. I need a review of my idea.
Let me introduce the context of this question.
Consider the following C++ code, in order to drop the array of pointers, we need to
void foo() {
T ** array_of_pointers = ...;
size_t size_of_array = ...;
for (int i = 0; i < size_of_array; i++) {
delete array_of_pointersp[i];
}
delete [] array_of_pointers;
}
However, in Rust, we can also build the above array_of_pointers
. You may ask why bother? However, I want to use it as a mock for some FFI codes which I will leave out to make this question simple and direct.
Here is my code. I have a RawCppPtr
which is a wrapper of T*
(which is not an array), and a RawCppPtrArr
which is a wrapper of T**
(which is an array of T*
). In this code, I will assume T
to be String. So I can create a String with gen_cpp_string
and put it into a Vec<RawCppPtr>
. Then I convert Vec<RawCppPtr>
into *mut [RawCppPtr]
, and finally into *mut RawCppPtr
. I know little about Fat/Thin pointer, so *mut [RawCppPtr]
is a fat pointer with array length, and *mut RawCppPtr
is a thin pointer which point to the first element of array(am I right?).
pub struct RawCppPtr {
pub ptr: *mut std::os::raw::c_void,
pub type_: u64,
}
impl Drop for RawCppPtr {
fn drop(&mut self) {
assert_eq!(self.type_, 1);
unsafe {
drop(Box::<Vec<u8>>::from_raw(self.ptr as *mut Vec<u8>));
}
}
}
extern "C" fn gen_cpp_string(s: &[u8]) -> RawCppPtr {
let str = Box::new(Vec::from(s));
let ptr = Box::into_raw(str);
RawCppPtr {
ptr: ptr as *mut _,
type_: 1,
}
}
unsafe impl Send for RawCppPtrArr {}
pub struct RawCppPtrArr {
pub inner: *mut RawCppPtr,
pub len: u64,
}
impl Drop for RawCppPtrArr {
fn drop(&mut self) {
unsafe {
let len = self.len;
// Delete all `T**`
for i in 0..len {
let i = i as usize;
let inner_i = self.inner.add(i);
drop(inner_i);
}
// Delete `T*`
drop(self.inner);
self.inner = std::ptr::null_mut();
self.len = 0;
}
}
}
fn main() {
let len = 10;
let mut v: Vec<RawCppPtr> = vec![];
for i in 0..len {
let s = format!("s{}", i);
v.push(gen_cpp_string(s.as_bytes()));
}
let box_v = v.into_boxed_slice();
// Convert from *mut [RawCppPtr] tp *mut RawCppPtr
let ptr_va = Box::into_raw(box_v);
println!("va {}", std::mem::size_of_val(&(ptr_va))); // 16 which means a fat pointer
let ptr_v = ptr_va as *mut RawCppPtr;
let cpp_ptr_arr = RawCppPtrArr {
inner: ptr_v,
len: len as u64,
};
println!("v {}", std::mem::size_of_val(&(ptr_v))); // 8 which means a thin pointer
drop(cpp_ptr_arr);
}
If I am right, the code leaks memory. All String
s are dropped, however, the array of RawCppPtr
is not correctly dropped. I should drop *mut [RawCppPtr]
rather than *mut RawCppPtr
to free this array.
I want to know how to correctly drop this array, which *mut RawCppPtr
and the length of the original *mut [RawCppPtr]
.
Please note that I don't want to use *mut [RawCppPtr]
because I am writing FFI codes. C++ doesn't has something like fat pointers.
==== UPDATE ====
After some searching, I find some from_raw_parts functions may help. However, I don't know if this is the best practice. And I also need some review of my whole idea.
A little more explanation about the FFI. In my program, Rust do knows when to drop RawCppPtr
. However, Rust do not know how to drop RawCppPtr
, since it is created by C++. So when Rust wants to drop RawCppPtr
, it will call by a FFI function, and the C++ part of my program will delete RawCppPtr
by checking its type. This scene make sense, for example if the RawCppPtr
is used as a context which held by Rust, but only meaningful to C++. When Rust no longer need to call C++, it will drop the RawCppPtr
.
Upvotes: 0
Views: 266
Reputation: 22728
If you are on Linux (or Windows with WSL), you can use the LLVM ASAN to find out if your code leaks memory.
First, install the llvm
package and add a nightly toolchain:
sudo apt install llvm
rustup install nightly
Then, clean and rerun your program on nightly with -Z sanitizer=address
:
cargo clean
RUSTFLAGS="-Z sanitizer=address" cargo +nightly run
In your case, this is what I get:
va 16
v 8
=================================================================
==1163==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 160 byte(s) in 1 object(s) allocated from:
#0 0x56160fa4f7a6 in realloc /rustc/llvm/src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:85:3
#1 0x56160fa82606 in alloc::alloc::realloc::hb51e4854c2cd558e /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/alloc.rs:132:14
#2 0x56160fa82606 in _$LT$alloc..alloc..Global$u20$as$u20$core..alloc..Allocator$GT$::shrink::h33a2c7a85dc19dd0 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/alloc.rs:300:31
#3 0x56160fa91e99 in alloc::raw_vec::RawVec$LT$T$C$A$GT$::shrink::h992b7dbe07a9cc09 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/raw_vec.rs:434:13
#4 0x56160fa8f0b2 in alloc::raw_vec::RawVec$LT$T$C$A$GT$::shrink_to_fit::hdabc7b2f8734a8b2 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/raw_vec.rs:353:24
#5 0x56160fa7a7c1 in rust_tmp::main::hb0e8c33aaec1d192 /home/finomnis/work/rust-tmp/src/main.rs:58:17
#6 0x56160fa8833a in core::ops::function::FnOnce::call_once::h59579e65705d84ac /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/core/src/ops/function.rs:507:5
#7 0x56160fa77104 in std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hfe28e02ef7178702 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/rt.rs:166:18
#8 0x56160faa694b in core::ops::function::impls::_$LT$impl$u20$core..ops..function..FnOnce$LT$A$GT$$u20$for$u20$$RF$F$GT$::call_once::ha21c39ffd5fcb6b0 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/core/src/ops/function.rs:606:13
#9 0x56160faa694b in std::panicking::try::do_call::hfcb5b7de6a967aa4 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panicking.rs:483:40
#10 0x56160faa694b in std::panicking::try::h852b554f2a216a1e /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panicking.rs:447:19
#11 0x56160faa694b in std::panic::catch_unwind::h138950e22f10d85b /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panic.rs:137:14
#12 0x56160faa694b in std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::h0de24c6b40b0e960 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/rt.rs:148:48
#13 0x56160faa694b in std::panicking::try::do_call::hd6b16c5e5da3ce3b /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panicking.rs:483:40
#14 0x56160faa694b in std::panicking::try::hb7de684c95f0cf1d /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panicking.rs:447:19
#15 0x56160faa694b in std::panic::catch_unwind::h8cc7b91a0a2687e5 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panic.rs:137:14
#16 0x56160faa694b in std::rt::lang_start_internal::hf69e88a76ee34b77 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/rt.rs:148:20
Indirect leak of 120 byte(s) in 5 object(s) allocated from:
#0 0x56160fa4f37e in malloc /rustc/llvm/src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:69:3
#1 0x56160fa7f599 in alloc::alloc::alloc::h65ebcea6dc0f57fc /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/alloc.rs:95:14
#2 0x56160fa7f599 in alloc::alloc::Global::alloc_impl::h3020a89bee822f51 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/alloc.rs:177:73
#3 0x56160fa7e904 in _$LT$alloc..alloc..Global$u20$as$u20$core..alloc..Allocator$GT$::allocate::he4c810c1d4972785 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/alloc.rs:237:9
#4 0x56160fa7e904 in alloc::alloc::exchange_malloc::h2608191d7b8c8b30 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/alloc.rs:326:11
#5 0x56160fa79d17 in alloc::boxed::Box$LT$T$GT$::new::hf0594a7f872c5b56 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/boxed.rs:220:9
#6 0x56160fa79d17 in rust_tmp::gen_cpp_string::h631935a5fa641aec /home/finomnis/work/rust-tmp/src/main.rs:16:15
#7 0x56160fa7ab33 in rust_tmp::main::hb0e8c33aaec1d192 /home/finomnis/work/rust-tmp/src/main.rs:55:16
#8 0x56160fa8833a in core::ops::function::FnOnce::call_once::h59579e65705d84ac /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/core/src/ops/function.rs:507:5
#9 0x56160fa77104 in std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hfe28e02ef7178702 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/rt.rs:166:18
#10 0x56160faa694b in core::ops::function::impls::_$LT$impl$u20$core..ops..function..FnOnce$LT$A$GT$$u20$for$u20$$RF$F$GT$::call_once::ha21c39ffd5fcb6b0 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/core/src/ops/function.rs:606:13
#11 0x56160faa694b in std::panicking::try::do_call::hfcb5b7de6a967aa4 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panicking.rs:483:40
#12 0x56160faa694b in std::panicking::try::h852b554f2a216a1e /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panicking.rs:447:19
#13 0x56160faa694b in std::panic::catch_unwind::h138950e22f10d85b /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panic.rs:137:14
#14 0x56160faa694b in std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::h0de24c6b40b0e960 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/rt.rs:148:48
#15 0x56160faa694b in std::panicking::try::do_call::hd6b16c5e5da3ce3b /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panicking.rs:483:40
#16 0x56160faa694b in std::panicking::try::hb7de684c95f0cf1d /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panicking.rs:447:19
#17 0x56160faa694b in std::panic::catch_unwind::h8cc7b91a0a2687e5 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panic.rs:137:14
#18 0x56160faa694b in std::rt::lang_start_internal::hf69e88a76ee34b77 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/rt.rs:148:20
Indirect leak of 10 byte(s) in 5 object(s) allocated from:
#0 0x56160fa4f37e in malloc /rustc/llvm/src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:69:3
#1 0x56160fa7f599 in alloc::alloc::alloc::h65ebcea6dc0f57fc /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/alloc.rs:95:14
#2 0x56160fa7f599 in alloc::alloc::Global::alloc_impl::h3020a89bee822f51 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/alloc.rs:177:73
#3 0x56160fa832e0 in _$LT$alloc..alloc..Global$u20$as$u20$core..alloc..Allocator$GT$::allocate::he4c810c1d4972785 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/alloc.rs:237:9
#4 0x56160fa8ebef in alloc::raw_vec::RawVec$LT$T$C$A$GT$::allocate_in::hae911a4c2fb5959f /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/raw_vec.rs:185:45
#5 0x56160fa87703 in alloc::raw_vec::RawVec$LT$T$C$A$GT$::with_capacity_in::h6636278b38918562 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/raw_vec.rs:131:9
#6 0x56160fa87703 in alloc::vec::Vec$LT$T$C$A$GT$::with_capacity_in::hdeada21f93fcb1dc /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/vec/mod.rs:673:20
#7 0x56160fa87703 in _$LT$T$u20$as$u20$alloc..slice..hack..ConvertVec$GT$::to_vec::h1fd64ee8d4bb65f9 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/slice.rs:157:25
#8 0x56160fa86a34 in alloc::slice::hack::to_vec::ha41d09eef56ea4e4 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/slice.rs:106:9
#9 0x56160fa86a34 in alloc::slice::_$LT$impl$u20$$u5b$T$u5d$$GT$::to_vec_in::h16897712ae3feab3 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/slice.rs:436:9
#10 0x56160fa86a34 in alloc::slice::_$LT$impl$u20$$u5b$T$u5d$$GT$::to_vec::h3d1191f6fd4dcfe0 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/slice.rs:411:9
#11 0x56160fa86a34 in _$LT$alloc..vec..Vec$LT$T$GT$$u20$as$u20$core..convert..From$LT$$RF$$u5b$T$u5d$$GT$$GT$::from::hb85a3961450a1d84 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/alloc/src/vec/mod.rs:3118:9
#12 0x56160fa79d08 in rust_tmp::gen_cpp_string::h631935a5fa641aec /home/finomnis/work/rust-tmp/src/main.rs:16:24
#13 0x56160fa7ab33 in rust_tmp::main::hb0e8c33aaec1d192 /home/finomnis/work/rust-tmp/src/main.rs:55:16
#14 0x56160fa8833a in core::ops::function::FnOnce::call_once::h59579e65705d84ac /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/core/src/ops/function.rs:507:5
#15 0x56160fa77104 in std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hfe28e02ef7178702 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/rt.rs:166:18
#16 0x56160faa694b in core::ops::function::impls::_$LT$impl$u20$core..ops..function..FnOnce$LT$A$GT$$u20$for$u20$$RF$F$GT$::call_once::ha21c39ffd5fcb6b0 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/core/src/ops/function.rs:606:13
#17 0x56160faa694b in std::panicking::try::do_call::hfcb5b7de6a967aa4 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panicking.rs:483:40
#18 0x56160faa694b in std::panicking::try::h852b554f2a216a1e /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panicking.rs:447:19
#19 0x56160faa694b in std::panic::catch_unwind::h138950e22f10d85b /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panic.rs:137:14
#20 0x56160faa694b in std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::h0de24c6b40b0e960 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/rt.rs:148:48
#21 0x56160faa694b in std::panicking::try::do_call::hd6b16c5e5da3ce3b /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panicking.rs:483:40
#22 0x56160faa694b in std::panicking::try::hb7de684c95f0cf1d /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panicking.rs:447:19
#23 0x56160faa694b in std::panic::catch_unwind::h8cc7b91a0a2687e5 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/panic.rs:137:14
#24 0x56160faa694b in std::rt::lang_start_internal::hf69e88a76ee34b77 /rustc/af3e06f1bf4ca49407562b1b84744e27905bea98/library/std/src/rt.rs:148:20
SUMMARY: AddressSanitizer: 290 byte(s) leaked in 11 allocation(s).
Now about the actual fixing.
There are several misconceptions you have:
drop(inner)
does NOT delete the object inner
is pointing to, it only deletes the pointergenerate_cpp_string
does not generate a C/C++ compatible string object.
It generates a pointer to a Vec
, which C/C++ knows absolutely nothing about. You probably want to create a boxed slice here as well.RawCppPtr
as a type in a extern 'C'
function, it has to be FFI safe. The same goes for &[u8]
, which also isn't FFI safe.Vec<u8>
as a dummy type, but don't do that, it is not compatible with C. Use something C compatible like f32
for demonstration purposes. Especially because you can't convert *mut u8
back to a slice and free it without knowing its length. Or use a custom C-compatible struct that stores both the pointer and the length.gen_cpp_string
to be called from Rust only, don't declare it extern "C"
. Only declare the functions that shall be called from C extern "C"
.Let's first approach the fact that you can't undo the conversion from *mut [u8]
to *mut u8
. One is a fat pointer and the other one is thin, so you need to store the size somewhere to convert back.
To achieve that, let's store it in a C compatible struct:
#[repr(C)]
pub struct CString {
data: *mut u8,
len: usize,
}
impl CString {
pub fn copy_from_slice(s: &[u8]) -> Self {
let boxed_slice = Vec::from(s).into_boxed_slice();
let len = boxed_slice.len();
let data = Box::into_raw(boxed_slice);
Self {
data: data.cast(),
len,
}
}
}
impl Drop for CString {
fn drop(&mut self) {
unsafe {
drop(Box::from_raw(std::slice::from_raw_parts_mut(
self.data, self.len,
)));
}
}
}
Next, let's talk about RawCppPtr
. For that one, your implemention seems to be mostly correct. All I did was replace Vec<u8>
with the C-compatible CString
and add #[repr(C)]
to make the struct layout FFI compatible:
#[repr(C)]
pub struct RawCppPtr {
pub ptr: *mut std::os::raw::c_void,
pub type_: u64,
}
impl Drop for RawCppPtr {
fn drop(&mut self) {
assert_eq!(self.type_, 1);
unsafe {
drop(Box::from_raw(self.ptr as *mut CString));
}
}
}
fn gen_cpp_string(s: &[u8]) -> RawCppPtr {
let str = Box::new(CString::copy_from_slice(s));
let ptr = Box::leak(str) as *mut CString;
RawCppPtr {
ptr: ptr.cast(),
type_: 1,
}
}
Now about RawCppPtrArr
: There are several problems here.
For one, I highly discourage creating an unsafe struct outside if its own implementation. Your code in main
that creates this struct should be a member function of it.
Further, the way you drop it doesn't actually do anything. You need to convert it back from pointers to actual objects. Dropping pointers does not actually drop the things they point to. The easiest, like in CString
, is to use from_raw_parts
:
#[repr(C)]
pub struct RawCppPtrArr {
pub inner: *mut RawCppPtr,
pub len: u64,
}
impl RawCppPtrArr {
pub fn from_vec(v: Vec<RawCppPtr>) -> Self {
let box_v = v.into_boxed_slice();
// Convert from *mut [RawCppPtr] tp *mut RawCppPtr
let len = box_v.len() as u64;
let ptr_va = Box::into_raw(box_v);
let ptr_v = ptr_va as *mut RawCppPtr;
Self { inner: ptr_v, len }
}
}
impl Drop for RawCppPtrArr {
fn drop(&mut self) {
unsafe {
drop(Box::from_raw(std::slice::from_raw_parts_mut(
self.inner,
self.len as usize,
)));
}
}
}
Now all together, with main
adjusted accordingly, we get:
#[repr(C)]
pub struct CString {
data: *mut u8,
len: u64,
}
impl CString {
pub fn copy_from_slice(s: &[u8]) -> Self {
let boxed_slice = Vec::from(s).into_boxed_slice();
let len = boxed_slice.len() as u64;
let data = Box::into_raw(boxed_slice);
Self {
data: data.cast(),
len,
}
}
}
impl Drop for CString {
fn drop(&mut self) {
unsafe {
drop(Box::from_raw(std::slice::from_raw_parts_mut(
self.data,
self.len as usize,
)));
}
}
}
#[repr(C)]
pub struct RawCppPtr {
pub ptr: *mut std::os::raw::c_void,
pub type_: u64,
}
impl Drop for RawCppPtr {
fn drop(&mut self) {
assert_eq!(self.type_, 1);
unsafe {
drop(Box::from_raw(self.ptr as *mut CString));
}
}
}
fn gen_cpp_string(s: &[u8]) -> RawCppPtr {
let str = Box::new(CString::copy_from_slice(s));
let ptr = Box::leak(str) as *mut CString;
RawCppPtr {
ptr: ptr.cast(),
type_: 1,
}
}
#[repr(C)]
pub struct RawCppPtrArr {
pub inner: *mut RawCppPtr,
pub len: u64,
}
impl RawCppPtrArr {
pub fn from_vec(v: Vec<RawCppPtr>) -> Self {
let box_v = v.into_boxed_slice();
// Convert from *mut [RawCppPtr] tp *mut RawCppPtr
let len = box_v.len() as u64;
let ptr_va = Box::into_raw(box_v);
let ptr_v = ptr_va as *mut RawCppPtr;
Self { inner: ptr_v, len }
}
}
impl Drop for RawCppPtrArr {
fn drop(&mut self) {
unsafe {
drop(Box::from_raw(std::slice::from_raw_parts_mut(
self.inner,
self.len as usize,
)));
}
}
}
fn main() {
let len = 10;
let mut v: Vec<RawCppPtr> = vec![];
for i in 0..len {
let s = format!("s{}", i);
v.push(gen_cpp_string(s.as_bytes()));
}
let cpp_ptr_arr = RawCppPtrArr::from_vec(v);
println!("v {}", std::mem::size_of_val(&(cpp_ptr_arr.inner))); // 8 which means a thin pointer
}
No need to drop(cpp_ptr_arr)
, this happens automatically at the end of main()
with all local variables.
Now if we run it again with the sanitizer:
$ RUSTFLAGS="-Z sanitizer=address" cargo +nightly run
v 8
And no leaks.
Converting a &str
to a &[u8]
does NOT add a trailing zero and the resulting byte slice is NOT C compatible. There is an easy fix, however: Replace the CString
struct we wrote with the compiler built-in CString
. This one handles zero terminations correctly.
Upvotes: 2