从 String 到 *const i8 的正确方法是什么?
What is the proper way to go from a String to a *const i8?
在我正在进行的编写 safe wrapper for the Cassandra C++ driver 的传奇故事中,我现在的目光转向了在调用带有如下签名的 C 函数时避免内存泄漏:
cass_string_init2(const char* data, cass_size_t length);
或
cass_string_init(const char* null_terminated);
我已经尝试了几种名义上可行的不同方法,并产生了正确的结果,但我还没有找到正确管理此数据生命周期的方法。下面是两个示例方法。
pub fn str_to_ref(mystr:&str) -> *const i8 {unsafe{
let cstr = CString::from_slice(mystr.as_bytes());
cstr.as_slice().as_ptr()
}}
和
pub fn str_to_ref(mystr: &str) -> *const i8 {
let l = mystr.as_bytes();
unsafe {
let b = alloc::heap::allocate(mystr.len()+1, 8);
let s = slice::from_raw_parts_mut(b, mystr.len()+1);
slice::bytes::copy_memory(s, l);
s[mystr.len()] = 0;
return b as *const i8;
}
}
第一个像
这样的无效内存访问
==26355== Address 0x782d140 is 0 bytes inside a block of size 320 free'd
==26355== at 0x1361A8: je_valgrind_freelike_block (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355== by 0x11272D: heap::imp::deallocate::h7b540039fbffea4dPha (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355== by 0x112679: heap::deallocate::h3897fed87b942253tba (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355== by 0x112627: vec::dealloc::h7978768019700822177 (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355== by 0x112074: vec::Vec$LT$T$GT$.Drop::drop::h239007174869221309 (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355== by 0x111F9D: collections..vec..Vec$LT$i8$GT$::glue_drop.5732::h978a83960ecb86a4 (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355== by 0x111F6D: std..ffi..c_str..CString::glue_drop.5729::h953a595760f34a9d (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355== by 0x112903: cql_ffi::helpers::str_to_ref::hef3994fa55168b90bqd (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
=
而第二个不知道何时释放其内存,导致:
==29782== 8 bytes in 1 blocks are definitely lost in loss record 1 of 115
==29782== at 0x12A5B2: je_mallocx (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==29782== by 0x1142D5: heap::imp::allocate::h3fa8a1c097e9ea53Tfa (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==29782== by 0x114221: heap::allocate::h18d191ce51ab2236gaa (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==29782== by 0x112874: cql_ffi::helpers::str_to_ref::h5b60f207d1e31841bqd (helpers.rs:25)
使用这两种方法中的任何一种作为起点,或者使用完全不同的方法,我将非常感谢您提供有关如何以正确的方式实现这一目标的指导。
编辑:
Shep 的回答 完美地 使用 cass_string_init 和 cass_string_init2 解决了我的问题。太感谢了。但是,我仍然不清楚将 *const i8 参数传递给其他函数,例如:
CASS_EXPORT CassError
cass_cluster_set_contact_points(CassCluster* cluster,
const char* contact_points);
它希望传递对空终止字符串的引用。
基于之前适用于 CassStrings 的方法以及 CString 文档,我得出以下结论:
pub struct ContactPoints(*const c_char);
pub trait AsContactPoints {
fn as_contact_points(&self) -> ContactPoints;
}
impl AsContactPoints for str {
fn as_contact_points(&self) -> ContactPoints {
let cstr = CString::new(self).unwrap();
let bytes = cstr.as_bytes_with_nul();
let ptr = bytes.as_ptr();
ContactPoints(ptr as *const i8)
}
}
(过多的 let 绑定只是为了确保我没有遗漏任何细微之处)
并且运行正确,但 valgrind 抱怨:
==22043== Invalid read of size 1
==22043== at 0x4C2E0E2: strlen (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==22043== by 0x4F8AED8: cass_cluster_set_contact_points (in /usr/local/lib/libcassandra.so.1.0.0)
==22043== by 0x11367A: cql_ffi::cluster::CassCluster::set_contact_points::h575496cbf7644b9e6oa (cluster.rs:76)
Cassandra C API for cass_string_init2 看起来像:
Note: This does not allocate memory. The object wraps the pointer passed into this function.
CASS_EXPORT CassString
cass_string_init2(const char* data, cass_size_t length);
也就是说,它接受一个字符串,和return是字符串的另一种表示形式。那 representation looks like:
typedef struct CassString_ {
const char* data;
cass_size_t length;
} CassString;
这是您要在 Rust 代码中使用 #[repr(C)]
的地方:
#[repr(C)]
struct CassStr {
data: *const c_char,
length: size_t,
}
你能做的最好的事情就是让字符串自动转换成这个结构:
trait AsCassStr {
fn as_cass_str(&self) -> CassStr;
}
impl AsCassStr for str {
fn as_cass_str(&self) -> CassStr {
CassStr {
data: self.as_bytes(),
length: self.len(),
}
}
}
然后让您的 API 接受任何实现 AsCassStr
的东西。这也允许您拥有自己的变体。您可能还想查看 PhantomData
以允许强制执行 CassStr
对象的生命周期。
注意通常你想使用CString
来避免字符串内部NUL字节。但是,由于 API 接受一个长度参数,它可能本身就支持它们。您需要进行实验才能找出答案。如果没有,那么您需要使用 CString
,如下所示。
后半题
让我们逐行查看您的函数:
impl AsContactPoints for str {
fn as_contact_points(&self) -> ContactPoints {
let cstr = CString::new(self).unwrap(); // 1
let bytes = cstr.as_bytes_with_nul(); // 2
let ptr = bytes.as_ptr(); // 3
ContactPoints(ptr as *const i8) // 4
} // 5
}
- 我们创建一个新的
CString
。这会在某处分配一点内存,验证字符串没有 内部 NUL 字节,然后逐字节复制我们的字符串,并添加尾随零。
- 我们得到一个切片,它引用我们在步骤 1 中复制和验证的字节。回想一下,切片是一个指向数据的指针加上一个长度。
- 我们将切片转换为指针,忽略长度。
- 我们将指针存储在一个结构中,将其用作我们的 return 值
- 函数退出,释放所有局部变量。请注意
cstr
是一个局部变量,因此它持有的字节同样被释放。您现在有一个 悬挂指针 。不好!
您需要确保 CString
能活到需要的时间。希望您正在调用的函数不会保留对它的引用,但是没有简单的方法可以从函数签名中分辨出来。您可能需要如下代码:
fn my_cass_call(s: &str) {
let s = CString::new(s).unwrap();
cass_call(s.as_ptr()) // `s` is still alive here
}
这里的好处是您永远不会将指针存储在您自己的变量中。请记住,原始指针没有生命周期,因此您必须非常小心!
在我正在进行的编写 safe wrapper for the Cassandra C++ driver 的传奇故事中,我现在的目光转向了在调用带有如下签名的 C 函数时避免内存泄漏:
cass_string_init2(const char* data, cass_size_t length);
或
cass_string_init(const char* null_terminated);
我已经尝试了几种名义上可行的不同方法,并产生了正确的结果,但我还没有找到正确管理此数据生命周期的方法。下面是两个示例方法。
pub fn str_to_ref(mystr:&str) -> *const i8 {unsafe{
let cstr = CString::from_slice(mystr.as_bytes());
cstr.as_slice().as_ptr()
}}
和
pub fn str_to_ref(mystr: &str) -> *const i8 {
let l = mystr.as_bytes();
unsafe {
let b = alloc::heap::allocate(mystr.len()+1, 8);
let s = slice::from_raw_parts_mut(b, mystr.len()+1);
slice::bytes::copy_memory(s, l);
s[mystr.len()] = 0;
return b as *const i8;
}
}
第一个像
这样的无效内存访问==26355== Address 0x782d140 is 0 bytes inside a block of size 320 free'd
==26355== at 0x1361A8: je_valgrind_freelike_block (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355== by 0x11272D: heap::imp::deallocate::h7b540039fbffea4dPha (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355== by 0x112679: heap::deallocate::h3897fed87b942253tba (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355== by 0x112627: vec::dealloc::h7978768019700822177 (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355== by 0x112074: vec::Vec$LT$T$GT$.Drop::drop::h239007174869221309 (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355== by 0x111F9D: collections..vec..Vec$LT$i8$GT$::glue_drop.5732::h978a83960ecb86a4 (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355== by 0x111F6D: std..ffi..c_str..CString::glue_drop.5729::h953a595760f34a9d (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==26355== by 0x112903: cql_ffi::helpers::str_to_ref::hef3994fa55168b90bqd (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
=
而第二个不知道何时释放其内存,导致:
==29782== 8 bytes in 1 blocks are definitely lost in loss record 1 of 115
==29782== at 0x12A5B2: je_mallocx (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==29782== by 0x1142D5: heap::imp::allocate::h3fa8a1c097e9ea53Tfa (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==29782== by 0x114221: heap::allocate::h18d191ce51ab2236gaa (in /home/tupshin/workspaces/rust/cql-ffi/target/basic)
==29782== by 0x112874: cql_ffi::helpers::str_to_ref::h5b60f207d1e31841bqd (helpers.rs:25)
使用这两种方法中的任何一种作为起点,或者使用完全不同的方法,我将非常感谢您提供有关如何以正确的方式实现这一目标的指导。
编辑:
Shep 的回答 完美地 使用 cass_string_init 和 cass_string_init2 解决了我的问题。太感谢了。但是,我仍然不清楚将 *const i8 参数传递给其他函数,例如:
CASS_EXPORT CassError
cass_cluster_set_contact_points(CassCluster* cluster,
const char* contact_points);
它希望传递对空终止字符串的引用。
基于之前适用于 CassStrings 的方法以及 CString 文档,我得出以下结论:
pub struct ContactPoints(*const c_char);
pub trait AsContactPoints {
fn as_contact_points(&self) -> ContactPoints;
}
impl AsContactPoints for str {
fn as_contact_points(&self) -> ContactPoints {
let cstr = CString::new(self).unwrap();
let bytes = cstr.as_bytes_with_nul();
let ptr = bytes.as_ptr();
ContactPoints(ptr as *const i8)
}
}
(过多的 let 绑定只是为了确保我没有遗漏任何细微之处)
并且运行正确,但 valgrind 抱怨:
==22043== Invalid read of size 1
==22043== at 0x4C2E0E2: strlen (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==22043== by 0x4F8AED8: cass_cluster_set_contact_points (in /usr/local/lib/libcassandra.so.1.0.0)
==22043== by 0x11367A: cql_ffi::cluster::CassCluster::set_contact_points::h575496cbf7644b9e6oa (cluster.rs:76)
Cassandra C API for cass_string_init2 看起来像:
Note: This does not allocate memory. The object wraps the pointer passed into this function.
CASS_EXPORT CassString cass_string_init2(const char* data, cass_size_t length);
也就是说,它接受一个字符串,和return是字符串的另一种表示形式。那 representation looks like:
typedef struct CassString_ {
const char* data;
cass_size_t length;
} CassString;
这是您要在 Rust 代码中使用 #[repr(C)]
的地方:
#[repr(C)]
struct CassStr {
data: *const c_char,
length: size_t,
}
你能做的最好的事情就是让字符串自动转换成这个结构:
trait AsCassStr {
fn as_cass_str(&self) -> CassStr;
}
impl AsCassStr for str {
fn as_cass_str(&self) -> CassStr {
CassStr {
data: self.as_bytes(),
length: self.len(),
}
}
}
然后让您的 API 接受任何实现 AsCassStr
的东西。这也允许您拥有自己的变体。您可能还想查看 PhantomData
以允许强制执行 CassStr
对象的生命周期。
注意通常你想使用CString
来避免字符串内部NUL字节。但是,由于 API 接受一个长度参数,它可能本身就支持它们。您需要进行实验才能找出答案。如果没有,那么您需要使用 CString
,如下所示。
后半题
让我们逐行查看您的函数:
impl AsContactPoints for str {
fn as_contact_points(&self) -> ContactPoints {
let cstr = CString::new(self).unwrap(); // 1
let bytes = cstr.as_bytes_with_nul(); // 2
let ptr = bytes.as_ptr(); // 3
ContactPoints(ptr as *const i8) // 4
} // 5
}
- 我们创建一个新的
CString
。这会在某处分配一点内存,验证字符串没有 内部 NUL 字节,然后逐字节复制我们的字符串,并添加尾随零。 - 我们得到一个切片,它引用我们在步骤 1 中复制和验证的字节。回想一下,切片是一个指向数据的指针加上一个长度。
- 我们将切片转换为指针,忽略长度。
- 我们将指针存储在一个结构中,将其用作我们的 return 值
- 函数退出,释放所有局部变量。请注意
cstr
是一个局部变量,因此它持有的字节同样被释放。您现在有一个 悬挂指针 。不好!
您需要确保 CString
能活到需要的时间。希望您正在调用的函数不会保留对它的引用,但是没有简单的方法可以从函数签名中分辨出来。您可能需要如下代码:
fn my_cass_call(s: &str) {
let s = CString::new(s).unwrap();
cass_call(s.as_ptr()) // `s` is still alive here
}
这里的好处是您永远不会将指针存储在您自己的变量中。请记住,原始指针没有生命周期,因此您必须非常小心!