ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

深入理解 Rust 的 `c_void`:核心源码实现、FFI 用法与 opaque 类型建模

2026/9/10 2:07:23 拓冰建站 浏览量
深入理解 Rust 的 `c_void`:核心源码实现、FFI 用法与 opaque 类型建模 深入理解 Rust 的c_void核心源码实现、FFI 用法与 opaque 类型建模【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust导读c_void是 Rust 标准库中用于对接 C 语言void*/const void*指针类型的关键类型是编写 FFI外部函数接口绑定、操作原生句柄与内存接口时几乎绕不开的基础设施。本文以 library/core/src/ffi/c_void.md 官方文档为骨架深入当前仓库源码讲解c_void的定义方式、与 C 类型体系的对应关系、绕过extern type之前的 opaque 类型建模方案以及它在标准库内部如VaList、Windows 进程扩展、LLVM 位码表示的真实落地用法帮助你写出正确、安全、可维护的 FFI 代码。一、c_void是什么C 的void*在 Rust 中的对应物c_void是 Rust 标准库提供的、与 C 语言void类型在指针语境下等价的核心 FFI 类型。它定义在 library/core/src/ffi/mod.rs 中通过include_str!把 c_void.md 的内容作为该类型的官方文档#[doc include_str!(c_void.md)] #[lang c_void] #[repr(u8)] #[stable(feature core_c_void, since 1.30.0)] pub enum c_void { #[unstable(feature c_void_variant, reason temporary implementation detail, issue none)] #[doc(hidden)] __variant1, #[unstable(feature c_void_variant, reason temporary implementation detail, issue none)] #[doc(hidden)] __variant2, }从源码可以看出几个关键事实它是一个枚举而不是单元结构体unit struct。#[repr(u8)]指明其内存表示占用一个字节私有且#[doc(hidden)]的两个变体__variant1、__variant2保证了外部代码永远无法构造或匹配c_void的实例。为什么需要两个变体mod.rs 第 39~46 行的注释说明得很清楚——编译器会抱怨repr属性需要一个以上的变体而至少要有一个变体否则枚举将是“无人居住”uninhabited的对这类指针解引用会构成未定义行为UB。所以两个变体是一个精心的工程折衷。为什么是枚举而非()这是为了让 LLVM 在生成位码时把*const c_void识别为i8*。mod.rs 的注释for LLVM to recognize the void pointer type and by extension functions like malloc()明确指出只有以i8*表示 void 指针malloc等 C 运行时函数才能被 LLVM 正确识别与优化。与 C 类型体系的等价关系官方文档给出最核心的对应关系*const c_void等价于 C 的const void**mut c_void等价于 C 的void*注意c_void不是 C 的void返回类型——C 的void返回类型对应 Rust 的()单元类型。也就是说声明一个 C 函数void foo(void)时在 Rust 里应写成extern C fn foo()返回值是()而绝不是c_void。版本与路径演进Rust 1.30.0 起c_void从std::os::raw迁入core::ffi并由std::ffi再导出library/std/src/ffi/mod.rs 中的pub use core::ffi::c_void;即为此再导出的实现std::os::raw目前只是兼容层在 library/std/src/os/raw/mod.rs 中用alias_core_ffi!宏把c_void等类型直接type别名为core::ffi::c_void其文档同样是include_str!引入的c_void.md历史背景可参考 RFC 2521c_void统一方案它推动了旧编译器时代std::os::raw::c_void与新路径core::ffi::c_void的归一化。如果你的代码要兼容早至 1.1.0 的编译器可以继续使用std::os::raw::c_void在 1.30.0 之后二者是同一类型。二、FFI 中的典型用法句柄、不透明指针与 C 运行时函数1. 把c_void用作“万能”指针参数/返回值在 C 侧大量 API 以void*传递任意数据或句柄。Rust 侧对应的就是*mut c_void或*const c_void。当前仓库中一个非常典型的示例来自 library/std/src/os/windows/process.rs 的文档示例调用 Windows APICreatePipe时其安全属性指针lppipeattributes的类型即*const c_void传std::ptr::null()表示使用默认安全属性#![feature(windows_process_extensions_raw_attribute)] use std::ffi::c_void; use std::os::windows::process::{CommandExt, ProcThreadAttributeList}; use std::os::windows::raw::HANDLE; use std::process::Command; #[repr(C)] pub struct COORD { pub X: i16, pub Y: i16, } unsafe extern system { fn CreatePipe( hreadpipe: *mut HANDLE, hwritepipe: *mut HANDLE, lppipeattributes: *const c_void, // NULL 表示默认安全属性 nsize: u32, ) - i32; // ... }另一个常见场景是 Windows 句柄类型。在 library/std/src/os/windows/raw.rs 中HANDLE被定义为pub type HANDLE *mut c_void;也就是说Windows 的HANDLE在 Rust 中就是一个指向c_void的可变裸指针。整个std::os::windows::process扩展如ProcThreadAttributeList、伪控制台CreatePseudoConsole都围绕它工作相关代码可见 library/std/src/os/windows/process.rs。2. 标准库内部的真实使用VaList、malloc与回溯c_void不只是给用户用的标准库自身也在大量使用变参函数VaList是与 Cva_listABI 兼容的类型library/core/src/ffi/va_list.rs其内部VaListInner结构体在 x86_64、AArch64、RISC-V、MIPS、PowerPC 等架构上大量使用*const c_void字段如overflow_arg_area、reg_save_area、__current_saved_reg_area_pointer等见 va_list.rs。同时 va_list.rs 专门为*const c_void/*mut c_void实现了VaArgSafe标记 trait允许在 C 变参函数中读取 void 指针类型的参数。C 运行时函数在 library/core/src/ffi/mod.rs 的runtime_symbols模块中memcpy、memmove、memset、memcmp、bcmp等 C 运行时符号的原型全部使用*mut c_void/*const c_void。这也是“只有以i8*表示 void 指针 LLVM 才能正确识别malloc”这一设计动机的直接体现。回溯std的 backtrace 实现用fn ip(self) - *mut c_void表示指令指针library/std/src/backtrace.rs。三、如何在 FFI 中建模“不透明类型”opaque typesc_void最常见的误用是把*mut c_void直接当成一切不透明类型的万能容器。官方文档对此给出了明确的、更优的工程建议在extern type稳定之前要建模指向不透明类型的指针例如指向 C 侧未公开内部结构的struct Foo的指针推荐使用“包着一个空字节数组的新类型包装器”newtype wrapper around an empty byte array。具体做法是// 不透明类型的占位空字节数组 #[repr(C)] pub struct Opaque { _data: [u8; 0], } extern C { fn foo_new() - *mut Opaque; // 等价于 C 的 struct Foo* fn foo_set(obj: *mut Opaque, v: i32); fn foo_free(obj: *mut Opaque); }这种做法的意义在于相比*mut c_void*mut Opaque携带了类型信息编译器可以阻止你把foo_new()的返回值错误地传给另一个不透明类型的函数类型安全同时又不会引入对内部布局的任何假设。该方案的详细原理与历史可参见 Rust Nomicon 中 “Representing Opaque Structs” 一节官方文档 c_void.md 中给出的 [Nomicon] 链接。关于extern type的进展这是一个自 Rust 1.23.0 起就在 unstable 特性列表中跟踪的语言特性见 compiler/rustc_feature/src/unstable.rs跟踪 issue 为 43467。从当前仓库源码看它仍然处于 unstable 状态对应 feature gate 为extern_types因此在写稳定代码时空字节数组包装方案依然是官方推荐的默认做法。一旦extern type稳定就可以直接写extern C { type Foo; }来获得更自然的语法。四、Debug实现与安全注意c_void在 library/core/src/ffi/mod.rs 中实现了Debug#[stable(feature std_debug, since 1.16.0)] impl fmt::Debug for c_void { fn fmt(self, f: mut fmt::Formatter_) - fmt::Result { f.debug_struct(c_void).finish() } }打印一个c_void会输出形如c_void的结构体调试信息不展开任何字段这使你在调试 FFI 代码时可以安全地{:?}打印指针而无需关心其指向的内容。使用c_void时还需要牢记 Rust 裸指针的基本原则*const c_void/*mut c_void是裸指针编译器不保证其合法性解引用或传递给 C 函数属于unsafe操作不要把c_void与()混淆前者是指向任意字节的不透明指针的类型参数后者才是无返回值在声明 C 函数原型时务必保证参数/返回值类型与 C 头文件逐位一致void*对应*mut c_void或*const c_void取决于是否被修改void返回值对应()。五、快速参考何时用c_void何时不该用场景推荐写法声明 C 函数参数为void**mut c_void可写/*const c_void只读声明 C 函数返回void**mut c_void声明 C 函数返回void返回值写()建模不透明 C 结构体指针稳定版空字节数组 newtype 包装如struct Opaque { _data: [u8; 0] }建模不透明 C 结构体指针未来、unstableextern typefeature gateextern_typesWindowsHANDLE*mut c_void即 library/std/src/os/windows/raw.rs 中的类型别名C 变参函数中读取 void 指针参数VaList::next_arg::*const c_void()等配合VaArgSafe总结c_void是 Rust FFI 生态的基石类型它在 library/core/src/ffi/mod.rs 中被精心实现为带两个私有变体的#[repr(u8)]枚举既保证了 LLVM 能将其识别为i8*以便正确优化malloc/memcpy等 C 运行时函数又通过私有变体杜绝了用户构造实例的可能。它的等价关系很简单——*const c_void对应const void*、*mut c_void对应void*、void返回值对应()——而真正的高质量 FFI 代码在建模不透明类型时应优先采用空字节数组 newtype 包装直到extern type特性稳定。理解了它的定义动机、版本沿革与标准库内部用法VaList、HANDLE、backtrace你就能写出类型更安全、行为更可预期的 FFI 绑定。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考