Rust调用C++函数参数传递异常,参数左移错位
Rust与C++ FFI参数错位问题排查与解决
问题背景
在将C代码集成到Rust项目时,出现参数传递错位现象:Rust端传入的参数到C端后顺序偏移,导致参数值完全错误。
相关代码
build.rs
fn main() { cc::Build::new() .file("src/bindings.cc") .cargo_metadata(true) .cpp(true) .compile("bindings"); }
bindings.cc
extern "C" { leveldb::Status db_get( LevelDB* db, const char* key, size_t keylen, const char** val, size_t* vallen, bool fillCache ) { printf("0: %p, 1: %p, 2: %d, 3: %p, 4: %p, 5: %s\n", db, key, keylen, val, vallen, fillCache ? "true" : "false" ); leveldb::Status status; return status; } }
sys.rs
enum LevelDB {} // why? leveldb::Status has private property const char* // It doesnt matter, this is just a test code #[repr(C)] struct Status(NonNull<u8>); #[link(name = "bindings")] extern "C" { pub fn db_get( db: *mut LevelDB, key: *const u8, keylen: usize, val: *mut *const u8, vallen: *mut usize, fill_cache: bool ) -> Status; }
调用代码
println!("0: {:?}, 1: {:?}, 2: {:?}, 3: {:?}, 4: {:?}, 5: {}", self.raw.as_ptr(), key.as_ptr(), key.len(), &mut val, &mut len, options.fill_cache ); let status = sys::db_get( self.raw.as_ptr(), key.as_ptr(), key.len(), &mut val, &mut len, options.fill_cache );
现象
运行后输出对比:
# Rust端输出 0: 0x58aa2fc8a700, 1: 0x58aa2fc8a6e0, 2: 9, 3: 0x0, 4: 0, 5: true # C++端输出 0: 0x58aa2fc8a6e0, 1: 0x9, 2: 1439424216, 3: 0x7fff55cbdee0, 4: 0x1, 5: true
可见C++端接收的参数整体左移,完全不符合传入值。
问题根源
非POD类型返回值破坏调用约定:
leveldb::Status是C非POD(Plain Old Data)类型,C语言的调用约定不支持直接返回这类复杂对象。C编译器处理非POD返回值时,会自动插入一个隐藏的指针参数(用于接收返回对象的内存地址),但Rust的extern "C"声明没有处理这个隐藏参数,导致整个参数栈的偏移错位。格式符不匹配放大输出错误:
C++代码中用%d打印size_t类型,而size_t在64位系统是8字节,%d仅处理4字节,导致输出的参数值本身就存在错误,进一步干扰问题排查。
解决方案
1. 修改C++函数,通过输出参数返回leveldb::Status
将返回值改为void,新增指针参数传递返回对象的内存地址:
extern "C" { void db_get( LevelDB* db, const char* key, size_t keylen, const char** val, size_t* vallen, bool fillCache, leveldb::Status* out_status // 新增输出参数 ) { printf("0: %p, 1: %p, 2: %zu, 3: %p, 4: %p, 5: %s\n", db, key, keylen, val, vallen, fillCache ? "true" : "false" ); *out_status = leveldb::Status(); } }
2. 更新Rust的FFI声明
对应修改函数签名,移除返回值,新增输出参数,并确保Status的内存布局与C++端匹配:
enum LevelDB {} // 按leveldb::Status的实际内存布局定义字段,此处为示例 #[repr(C)] struct Status { state: *const u8, } #[link(name = "bindings")] extern "C" { pub fn db_get( db: *mut LevelDB, key: *const u8, keylen: usize, val: *mut *const u8, vallen: *mut usize, fill_cache: bool, out_status: *mut Status, // 新增输出参数 ); }
3. 调整Rust调用代码
println!("0: {:?}, 1: {:?}, 2: {:?}, 3: {:?}, 4: {:?}, 5: {}", self.raw.as_ptr(), key.as_ptr(), key.len(), &mut val, &mut len, options.fill_cache ); let mut status = Status { state: std::ptr::null() }; unsafe { sys::db_get( self.raw.as_ptr(), key.as_ptr(), key.len(), &mut val, &mut len, options.fill_cache, &mut status, ); }
4. 额外注意事项
- 必须确保Rust中
Status结构体的#[repr(C)]布局与C++端leveldb::Status完全一致,可通过LevelDB头文件确认字段定义。 - 编译时保证Rust与C++的目标架构一致(如均为x86_64或arm64),避免指针宽度差异引发问题。
- C++中打印
size_t必须使用%zu格式符,避免类型不匹配导致的输出错误。
内容的提问来源于stack exchange,提问作者Karel Matthieu L. Logro
相关产品推荐
相关产品推荐

