Rust转C字符串首字节被置零问题及代码有效性咨询
Rust FFI字符串转换首字节置零问题分析与修复
问题重现
原代码尝试将Rust字符串编码为Windows-1251的C风格零终止字符串供FFI调用,但运行后出现首字节被置零的异常:
原代码
use encoding::all::WINDOWS_1251; use encoding::{EncoderTrap, Encoding}; use std::ffi::{c_char, CString}; fn str_to_c_str(input: &str) -> *const c_char { let enc_input: Vec<u8> = WINDOWS_1251.encode(input, EncoderTrap::Strict).expect("TODO: panic message"); println!("enc_input {:?}", enc_input); let cstring: CString = CString::new(enc_input).expect("TODO: panic message"); let s1 = unsafe { std::slice::from_raw_parts(cstring.as_ptr(), 4) }; println!("str_to_c_str {:?}", s1); cstring.as_ptr() } fn main() { unsafe { let data = str_to_c_str("ABC"); let s2 = unsafe { std::slice::from_raw_parts(data, 4) }; println!("main {:?}", s2); } }
实际输出
enc_input [65, 66, 67] str_to_c_str [65, 66, 67, 0] main [0, 66, 67, 0]
依赖配置
[dependencies] libc = "0.2.0" encoding = "0.2" byteorder = "1.4.3"
问题原因
核心问题是悬空指针:
str_to_c_str函数中创建的CString是局部变量,函数执行完毕返回时,CString的析构函数会立即释放它管理的内存- 返回的
*const c_char指针此时指向已被回收的内存,后续访问时内存内容已被系统或其他操作覆盖,因此出现首字节置零的异常
修复方案
解决的核心是保证CString的生命周期覆盖指针的使用周期,避免悬空指针:
- 直接返回
CString对象而非裸指针,由调用方持有并管理其生命周期(即你改写后的代码思路) - 若必须返回裸指针,需手动延长
CString的生命周期(比如用Box::leak将内存泄漏,后续需手动调用C的free释放,但这种方式易引发内存泄漏,不推荐)
改写后代码的有效性
你提供的改写代码是完全有效的:
use encoding::all::WINDOWS_1251; use encoding::{EncoderTrap, Encoding}; use std::ffi::{c_char, CString}; unsafe fn ffi_call(arg: *const c_char) -> () { let s1 = unsafe { std::slice::from_raw_parts(arg, 4) }; println!("str_to_c_str {:?}", s1); } fn str_to_c_str(input: &str) -> CString { let enc_input: Vec<u8> = WINDOWS_1251.encode(input, EncoderTrap::Strict).expect("TODO: panic message"); println!("enc_input {:?}", enc_input); let cstring: CString = CString::new(enc_input).expect("TODO: panic message"); cstring } fn main() { let data = str_to_c_str("ABC"); // 1 unsafe { ffi_call(data.as_ptr()); } // 2 }
main函数中的data变量持有CString对象,其生命周期从创建到main函数结束- 调用
ffi_call时,data仍处于存活状态,as_ptr()返回的指针指向有效内存,FFI调用能正确读取字符串内容 - 运行后会输出预期的
[65, 66, 67, 0]
关于编译器优化的疑问
编译器不会提前释放CString的内存:
Rust的所有权系统严格保证,只要CString对象(这里的data)还在当前作用域内,编译器就不会对其进行提前销毁的优化。data在main函数中直到注释2之后才会被销毁,完全覆盖了FFI调用的周期,因此内存是安全有效的。
内容的提问来源于stack exchange,提问作者osseum
相关产品推荐
相关产品推荐

