news 2026/9/10 12:11:36

深入理解 Rust `core::ffi::c_longlong`:FFI 互操作中的 64 位 C 类型桥梁

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入理解 Rust `core::ffi::c_longlong`:FFI 互操作中的 64 位 C 类型桥梁

深入理解 Rustcore::ffi::c_longlong:FFI 互操作中的 64 位 C 类型桥梁

【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust

c_longlong是 Rust 标准库core::ffi模块中用于表示 C 语言signed long long类型的平台相关类型别名,是 Rust 与 C 代码互操作(FFI)时处理 64 位有符号整数的规范工具。本文基于 Rust 官方仓库中该类型的定义文档与源码实现,讲清它的语义承诺、目标平台上的实际映射、在 FFI 声明与可变参数调用中的用法,以及它与c_long等相邻类型的差异,帮助你在编写extern "C"接口时选对类型、避免跨平台位宽陷阱。

c_longlong 的语义承诺:几乎总是 i64

c_longlong的官方定义文档 c_longlong.md 给出的核心表述是:

Equivalent to C'ssigned long long(long long) type.

This type will almost always bei64, but may differ on some systems. The C standard technically only requires that this type be a signed integer that is at least 64 bits and at least the size of along, although in practice, no system would have along longthat is not ani64, as most systems do not have a standardisedi128type.

这里包含三层关键信息,值得逐条理解:

  1. 等价对象c_longlong等价于 C 的long long(有符号形式),而不是 Rust 的某个固定位宽类型。它的存在意义是让 Rust 的 FFI 签名与 C 头文件中的long long参数精确对齐。
  2. 理论下限:C 标准只要求long long是「至少 64 位、且至少与long一样大」的有符号整数。也就是说,标准上并不排除某个假想平台上long long为 128 位(即对应 Rust 的i128)的可能。
  3. 工程现实:由于目前没有主流 C 工具链将 128 位整数标准化为long long的实现,实践中几乎所有平台的long long都是 64 位。文档中「almost always」的措辞正是为了在标准语义与工程实践之间保持诚实。

源码中的定义机制:type_alias 宏与文档内嵌

从源码结构看,c_longlong并不是一个独立的 Rust 类型,而是一个条件编译驱动的类型别名。其定义位于 primitives.rs:

type_alias! { "c_longlong.md", c_longlong = i64; } type_alias! { "c_ulonglong.md", c_ulonglong = u64; }

type_alias!宏在同文件的 第 6~16 行 定义,其展开逻辑是:

macro_rules! type_alias { { $Docfile:tt, $Alias:ident = $Real:ty; $( $Cfg:tt )* } => { #[doc = include_str!($Docfile)] $( $Cfg )* #[stable(feature = "core_ffi_c", since = "1.64.0")] pub type $Alias = $Real; } }

从源码结构看,这解释了三个工程细节:

  • 文档即代码的一部分c_longlong.md通过include_str!内嵌为类型别名上的#[doc]属性,这正是你在 rustdoc 中看到的说明文字。仓库把每个 C 类型的文档拆成独立.md文件(如 c_long.md),便于维护时精确控制每段 API 文档。
  • 稳定性版本:宏统一为这些别名打上#[stable(feature = "core_ffi_c", since = "1.64.0")],因此c_longlong自 Rust 1.64.0 起可在稳定版代码中使用。core::ffi模块本身的c_void更早,自 1.30.0 稳定(见 mod.rs)。
  • 与目标相关类型的区分:注意c_charc_intc_long等别名指向c_xxx_definition私有模块(内部用cfg_select!按目标架构展开),而c_longlong直接写死为i64,没有任何条件分支——这与文档「几乎总是 i64」的承诺在代码层面是一致的:维护者选择为它保留理论上的差异空间(文档措辞),但当前所有目标统一映射到i64

类型最终通过 mod.rs 的pub use重导出到core::ffi

mod primitives; #[stable(feature = "core_ffi_c", since = "1.64.0")] pub use self::primitives::{ c_char, c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, c_ulonglong, c_ushort, };

std侧同样将其透传,见 std 的 ffi 模块,因此std::ffi::c_longlongcore::ffi::c_longlong指向同一别名。

对照整个 C 类型家族:谁随目标变化,谁恒定

理解c_longlong的最好方式是把它放进core::ffi的完整类型表中。结合 primitives.rs 中各别名右侧的实际类型,可以得到如下映射(依据当前仓库源码):

C 类型Rust 别名当前映射是否随目标变化
long longc_longlongi64
unsigned long longc_ulonglongu64
longc_longi32i64
int/unsigned intc_int/c_uinti32/u32(avr、msp430 上为i16/u16
charc_chari8u8
doublec_doublef64(avr 上为f32

其中与c_longlong对比最常被踩坑的是c_long。其定义在 primitives.rs 的 c_long_definition 模块:

crate::cfg_select! { any( all(target_pointer_width = "64", not(windows)), // wasm32 Linux ABI uses 64-bit long all(target_arch = "wasm32", target_os = "linux") ) => { pub(super) type c_long = i64; pub(super) type c_ulong = u64; } _ => { // The minimal size of `long` in the C standard is 32 bits pub(super) type c_long = i32; pub(super) type c_ulong = u32; } }

这意味着:在 64 位 Linux/macOS 上c_longi64,而在 Windows(以及 32 位目标)上是i32。这正是官方文档 c_long.md 强调的差异点。实践结论很直接:

  • 当 C 接口签名中写的是long long(例如strtolltime_t在多数平台、64 位文件偏移off_t常见展开)时,用c_longlong,跨平台位宽恒为 64;
  • 当 C 签名写的是long时,用c_long,并接受它在不同平台上的位宽差异;
  • 如果 C 头文件里显式写int64_t,那才是真正的「固定 64 位」承诺——此时用c_longlong通常安全,但严格起见应确认该平台的int64_t基础类型。

另外,c_size_tc_ptrdiff_tc_ssize_t这三个别名仍标记为 unstable(featurec_size_t,跟踪 issue 88345,见 primitives.rs 第 156~175 行),当前均等价于usize/isize;而c_longlong是稳定 API,不需要任何 feature gate。

实战用法一:extern "C" 函数声明

c_longlong的典型出场位置是extern "C"块。假设某 C 库提供如下头文件:

// c 库头文件 int64_t compute_hash(const char *s, long long len);

对应 Rust 侧声明(len参数按 C 签名用c_longlong对齐;若库实际写long long,这是最贴切的映射):

use core::ffi::{c_char, c_int, c_longlong}; // 或等价地:use std::ffi::{c_char, c_int, c_longlong}; unsafe extern "C" { // 注意:返回类型若头文件是 int64_t,同样可用 c_longlong 对齐; // 若头文件写 long,则应使用 c_long 而不是 c_longlong。 fn compute_hash(s: *const c_char, len: c_longlong) -> c_longlong; }

关键要点:

  1. 签名逐字对齐:FFI 的类型错配(如把long写成c_longlong、在 Windows 上把c_long当作i64)是典型的未定义行为来源。上表中的「是否随目标变化」列就是排查依据。
  2. 别名而非裸整数:虽然当前目标上c_longlong展开就是i64,仍建议使用别名——它表达的是「这里对应 C 的long long」这一语义,且在理论上某目标出现差异时,别名比裸i64更稳健(这正是c_long已实际发生的差异)。
  3. unsafe边界:调用方需保证参数指针有效、值域与 C 侧一致;对*const c_char等参数,可读到的内容需符合 C 侧预期(如 NUL 结尾字符串语义由 C 约定)。

实战用法二:读取 C 可变参数(VaList)

c_longlong还有一个较少被注意但很实用的场景:Rust 1.99.0 起稳定了core::ffi的 C 可变参数支持(featurec_variadic)。在 va_list.rs 中:

  • VaArgSafe文档明确列出c_intc_long与 [c_longlong](以及无符号对应物、c_double、裸指针)是保证可读取的变参类型,而i32/usize等原生类型不应被直接依赖(因为它们在部分平台上可能不适用),见 VaArgSafe 文档;
  • unsafe impl VaArgSafe for i64u64(即c_longlong/c_ulonglong的底层类型)在所有目标上都成立,见 第 347~359 行;
  • 库内还有一段const _编译期断言va_arg_safe_check::<crate::ffi::c_longlong>()(第 424~443 行),确保别名与其底层类型的VaArgSafe实现保持一致。

因此,桥接 C 的printf风格可变参数函数时,应使用c_longlong而非i64来读取 64 位整数实参——这与「C 变参中比c_int小的整数会被整型提升」的安全契约相一致,该契约同样写在VaArgSafe的文档注释中。

与 C 标准的对照:为什么「至少 64 位」仍恒为 i64

回到定义文档的第一性原理。C 标准对long long的约束是intlonglong long且位宽至少 64 位。从源码结构看,仓库对这条约束的处理方式是「文档声明理论空间 + 代码固定映射」:c_longlong的别名定义不进入cfg_select!分支(对比c_long_definition的双分支实现),即当前 Rust 支持的所有目标上它都是i64。文档措辞保留「may differ on some systems」,是为未来可能出现非标准 64 位long long的目标(例如假想的 128 位long long)留出语义余地;在VaArgSafe的注释中也能看到仓库对「平台无__int128类型时不提供 128 位变参支持」这类平台差异的一致处理思路(见 第 361~414 行),可作为理解这一类型族整体设计哲学的旁证。

小结

  • c_longlong等价于 C 的signed long long,当前在所有 Rust 目标上稳定映射为i64(定义,自 1.64.0 稳定)。
  • 它是core::ffi类型族中位宽「恒定」的代表;与它的c_long(32/64 位随平台)、c_char(有符号性随平台)不同,跨平台 FFI 中表达 64 位有符号量应优先选择它。
  • 适用场景包括extern "C"声明中对应 C 头文件的long long参数,以及 C 可变参数函数中通过VaList安全读取 64 位整数实参。
  • 选型口诀:C 签名写long longc_longlong;写longc_long;写固定位宽整数(int64_t)时确认平台基础类型后再定。

【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 12:11:27

MATLAB数值转字符:核心技巧与工程实践

1. MATLAB数值转字符的核心价值与应用场景在工程计算和科研数据分析中&#xff0c;我们经常需要将数值结果转换为可读性更强的字符形式。MATLAB作为科学计算领域的标准工具&#xff0c;其数值转字符功能远不止简单的类型转换&#xff0c;而是数据呈现、报告生成和可视化标注的基…

作者头像 李华
网站建设 2026/9/10 12:10:57

MyBatis-Plus与Spring依赖注入整合实践指南

1. MyBatis-Plus与Spring依赖注入的深度整合实践在企业级Java开发中&#xff0c;MyBatis-Plus作为MyBatis的增强工具&#xff0c;与Spring框架的依赖注入机制结合使用&#xff0c;能够显著提升开发效率和代码质量。这种组合已经成为现代Java后端开发的标配方案之一。1.1 技术栈…

作者头像 李华
网站建设 2026/9/10 12:10:47

Serenity 的 slugify:文本转 slug 转换工具及其底层实现解析

Serenity 的 slugify&#xff1a;文本转 slug 转换工具及其底层实现解析 【免费下载链接】serenity The Serenity Operating System &#x1f41e; 项目地址: https://gitcode.com/GitHub_Trending/se/serenity slugify 是 Serenity OS 提供的一个命令行文本转 slug&…

作者头像 李华
网站建设 2026/9/10 12:09:59

CANN/GE图引擎Tensor描述符API

aclTensorDesc 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、TensorFlow …

作者头像 李华