惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
爱范儿
爱范儿
V
Visual Studio Blog
The Register - Security
The Register - Security
P
Proofpoint News Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
H
Hackread – Cybersecurity News, Data Breaches, AI and More
GbyAI
GbyAI
Y
Y Combinator Blog
M
MIT News - Artificial intelligence
大猫的无限游戏
大猫的无限游戏
L
LangChain Blog
The Cloudflare Blog
Hugging Face - Blog
Hugging Face - Blog
Microsoft Azure Blog
Microsoft Azure Blog
T
Threatpost
P
Proofpoint News Feed
美团技术团队
A
About on SuperTechFans
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
MongoDB | Blog
MongoDB | Blog
C
Check Point Blog
Vercel News
Vercel News
L
Lohrmann on Cybersecurity
N
News and Events Feed by Topic
宝玉的分享
宝玉的分享
T
Tor Project blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Cisco Blogs
博客园 - 司徒正美
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Cyberwarzone
Cyberwarzone
C
Cybersecurity and Infrastructure Security Agency CISA
S
Security @ Cisco Blogs
AWS News Blog
AWS News Blog
SecWiki News
SecWiki News
I
InfoQ
PCI Perspectives
PCI Perspectives
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Hacker News - Newest:
Hacker News - Newest: "LLM"
Latest news
Latest news
Stack Overflow Blog
Stack Overflow Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
H
Help Net Security
B
Blog RSS Feed
H
Hacker News: Front Page
雷峰网
雷峰网
Know Your Adversary
Know Your Adversary

博客园 - PKICA

rust性能优化与安全边界 联合体union在rust和C语言中有什么区别 rust并发与异步编程 如何预防rust不良的代码设计总结 rust类型系统与零成本抽象 rust内存模型 告别大显存依赖!用 Rust 新一代深度学习框架 Burn 打造纯 CPU 文本分类推理引擎 rust类型系统标记 编译配置解答 git实用命令 rust底层设计理念值得注意的几个地方总结 rust可变引用作为函数参数的机理详解 Rust内存重解释transmute Rust FFI 安全抽象范式 rust延迟初始化原语 rust重借用机制与原理 rust参数传递模型 汇编语言语法详解 gdb汇编调试 gdb-pwndbg的安装与使用指南 gdb调试插件gef C语言thread_local linux系统readelf命令使用指南 gcore转储进程内存 gdb查看命令 RGB与YUV颜色编码的区别 Rust原子类型 C++ STL求两个集合交集差集 gdb调试集锦 ubuntu24.0.4使用root用户登录 ubuntu24.0.4输入密码后跳回登录界面 AI内存压缩技术TurboQuant及存疑 ubuntu切换到指定内核版本 在没有顶级科技大佬直接背书的情况下deepseek为啥能够异军突起? HuggingFace和deepseek的关系 当前主流AI大模型 Rust写时克隆Cow系列2
C与Rust类型映射
PKICA · 2026-06-02 · via 博客园 - PKICA

在C/Rust 混合开发(FFI)中,通过 cbindgen 工具链将 Rust 侧函数的参数类型自动导出并对齐为 C 语言侧的参数类型,需要遵循以下一套精准的类型映射与工程防守规范:

1、 核心类型映射对照表

Rust 侧函数参数声明cbindgen 翻译后的 C 侧参数形式适用场景与安全语意
*mut std::os::raw::c_void void * 传递通用状态机指针
*const u8 const uint8_t * 高频传入的只读字节流切片(Payload 数据)
*mut core::NuclearPower NuclearPower * 对应 C 侧可变流对象(允许 Rust 侧读写流状态)
*const core::NuclearPower const NuclearPower * 对应 C 侧只读流对象(若 C 侧为 NuclearPower *,需注意常量限制)
&mut core::SpaceShipState SpaceShipState * 内存层面与裸指针完全等价,但在 Rust 内部具备非空安全保证
i32u8或 u32 int32_tuint8_t 或 uint32_t 跨语言返回码(注意:改变返回值类型会彻底改写 C 侧头文件)

2、 两端参数对齐的核心工程经验

  • 消除函数签名的 unsafe 限制:
    • 不要在导出的函数签名上写 pub unsafe extern "C" fn(这会导致 Rust 内部框架判定为类型不匹配)。
    • 正确做法:写成普通的 pub extern "C" fn,将所有的不安全操作(如指针解引用、transmute)牢牢锁在函数体内部的 unsafe { ... } 块中。
  • 指针判空的“地道”写法语意:
    • 摒弃 C 语言思维的 input != std::ptr::null_mut()
    • 一律采用 Rust 标准、纯安全的 !input.is_null()state.is_null() 表达式,避免低版本 Rust引发类型推导歧义。
  • 应对 cbindgen 类型冲突的“两全其美”方案:
    • 面临冲突:当你扩展一个已有的老函数,为了迎合全新的 Rust框架规范而不得不修改参数类型(如 u8i32*mut&mut),这会导致 cbindgen 刷新 C 头文件,进而引发历史 C 侧老代码全面崩溃。
    • 解决方案一(零侵入包装模式):原版导出函数一字不改。在旁边编写一个不带 #[no_mangle] 属性的普通 Rust 内部函数(cbindgen 会自动忽略),在内部做好类型强转(如 res as i32),专门挂载给 RustHandler 回调使用。
    • 解决方案二(类型别名欺骗):在 Rust 侧使用 type NuclearPowerPtr = *const core::NuclearPower; 完美对接 Rust侧回调的强类型检查,同时在 cbindgen.toml 中配置 [export.rename] "NuclearPowerPtr" = "NuclearPower *",强行在 C 侧生成不带 const 的纯净指针,完美实现两端不改代码的闭环对齐。

*mut core::SpaceShipState(裸指针)和 &mut core::SpaceShipState(可变引用)在 Rust 中有本质区别。它们的安全保证、编译器行为和使用场景完全不同。


📊 核心区别对比

特性裸指针 (*mut T)可变引用 (&mut T)
安全性 不安全。由程序员保证正确性。 绝对安全。由编译器检查。
是否能为 Null 可以。能指向 0x0 空地址。 绝对不能。必须指向有效内存。
别名规则 (Aliasing) 允许。多个裸指针可以指向同一块内存。 独占。同一时间只能有一个可变引用。
解引用 (使用数据) 必须在 unsafe {} 块中执行。 自动解引用,或者直接用 *。完全安全。
生命周期 忽略生命周期。可能变成野指针(悬垂指针)。 严格受生命周期约束,绝不会指向已释放内存。

🛠️ 代码实例对比

通过下面这个修改飞船燃料的例子,你可以直观看到它们的行为差异:

struct SpaceShipState {
    fuel: u32,
}

fn main() {
    let mut ship = SpaceShipState { fuel: 100 };

    // 1. 可变引用:安全、独占
    let r1: &mut SpaceShipState = &mut ship;
    r1.fuel = 90; // 正常修改
    // let r2: &mut SpaceShipState = &mut ship; // ❌ 报错!Rust 不允许同时存在两个可变引用

    // 2. 裸指针:不安全、允许重复创建
    let p1: *mut SpaceShipState = &mut ship;
    let p2: *mut SpaceShipState = p1; // ✅ 合法!可以复制多个裸指针

    // 使用裸指针修改数据必须用 unsafe
    unsafe {
        (*p1).fuel = 80; 
        println!("当前燃料: {}", (*p2).fuel);
    }
} // ship 在这里被释放,但 p1 和 p2 依然指向这个地址(变成危险的野指针)

🎯 什么时候用哪一个?

  • &mut (可变引用):Rust 编程的绝大多数(99%)场景。只要在纯 Rust 环境下编写常规代码,都应该用它。
  • *mut (裸指针):仅用于底层开发。例如:与 C/C++ 代码交互(FFI)、编写操作系统/嵌入式驱动、或者在标准库底层手动操作内存(如实现自定义的链表或 Vector)。

在 Rust 中,要消除裸指针(*mut)带来的野指针(悬垂指针)风险,核心逻辑是:永远不要让裸指针的生命周期超过它所指向的数据的生命周期。

以下是具体、高效的解决方案:

1). 严格限制裸指针的生命周期

不要定义全局或长寿命的裸指针。将裸指针的创建和使用限制在一个局部的代码块(Block)或函数中,确保在数据被释放前,裸指针就已经用完并失效。

fn main() {
    let mut ship = SpaceShipState { fuel: 100 };

    // 用花括号限制指针的作用域
    {
        let p1: *mut SpaceShipState = &mut ship;
        let p2: *mut SpaceShipState = p1;

        unsafe {
            (*p1).fuel = 80;
        }
    } // p1 和 p2 在这里离开作用域并失效

    // 此时 ship 依然安全有效,可以在后续继续安全使用
    println!("Ship fuel: {}", ship.fuel); 
} // ship 在这里才安全释放

2). 使用完毕后显式清空(置为 Null)

裸指针本身没有析构函数(Drop),它不会自动变空。为了防止后续误用,可以在用完后手动将其赋值为 core::ptr::null_mut()。在 unsafe 块中解引用前,先检查其是否为空。

use core::ptr;

fn main() {
    let mut ship = SpaceShipState { fuel: 100 };
    let mut p1: *mut SpaceShipState = &mut ship;

    unsafe {
        if !p1.is_null() {
            (*p1).fuel = 80;
        }
    }

    // 用完立刻置空,消除野指针隐患
    p1 = ptr::null_mut(); 
}

3). 如果数据在堆上:转回 Box 手动释放

如果你的飞船数据是通过 Box::into_raw 分配在堆内存(Heap)上的,它永远不会自动释放。为了防止内存泄漏和野指针,必须在用完后使用 Box::from_raw 将其转回安全引用,触发自动释放。

fn main() {
    // 在堆上分配内存,并解绑所有权,拿到裸指针
    let p1: *mut SpaceShipState = Box::into_raw(Box::new(SpaceShipState { fuel: 100 }));
    let p2: *mut SpaceShipState = p1;

    unsafe {
        (*p1).fuel = 80;
    }

    // 必须安全地回收堆内存,否则就是内存泄漏+野指针
    unsafe {
        // 重新包装成 Box,离开作用域时会自动触发 drop 释放内存
        let _reclaimed = Box::from_raw(p1); 
    } 
    // 此时 p1 和 p2 彻底失效,不能再被访问
}

4). 终极方案:重新拥抱安全引用 &mut

裸指针本身不带生命周期标签(Lifetime)。如果你希望编译器帮你检查野指针错误,应该在必要时立刻通过 as_mut() 将裸指针转回安全的可变引用 &mut

fn main() {
    let mut ship = SpaceShipState { fuel: 100 };
    let p1: *mut SpaceShipState = &mut ship;

    unsafe {
        // 瞬间将裸指针转为带生命周期绑定的安全引用
        if let Some(ship_ref) = p1.as_mut() {
            // 在这个 if 块内,编译器会严格保护 ship_ref 不会变成野指针
            ship_ref.fuel = 80; 
        }
    } 
}

参考资料:

rust工程化实践卷III juler 2015-11