You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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++端接收的参数整体左移,完全不符合传入值。

问题根源

  1. 非POD类型返回值破坏调用约定:
    leveldb::Status是C非POD(Plain Old Data)类型,C语言的调用约定不支持直接返回这类复杂对象。C编译器处理非POD返回值时,会自动插入一个隐藏的指针参数(用于接收返回对象的内存地址),但Rust的extern "C"声明没有处理这个隐藏参数,导致整个参数栈的偏移错位。

  2. 格式符不匹配放大输出错误:
    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 10:50:06