Rust 指针地址提取与 Strict Provenance:深入解析 `ptr::addr()` API

发布时间:2026/9/11 20:37:33
Rust 指针地址提取与 Strict Provenance:深入解析 `ptr::addr()` API Rust 指针地址提取与 Strict Provenance深入解析ptr::addr()API【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust导读ptr::addr()是 Rust 1.84 起稳定的一组 Strict Provenance严格出处API 的核心方法之一用于从原始指针中提取地址部分。与常见的self as usize转换不同addr()在丢弃指针出处provenance的同时不会将其暴露expose从而避免了整数→指针往返转换的内在歧义。本文以 library/core/src/ptr/docs/addr.md 为骨架结合 library/core/src/ptr/mod.rs、const_ptr.rs、mut_ptr.rs 等源码实现带你完整掌握addr()的语义、实现原理、与with_addr/map_addr/expose_provenance的协作方式以及在标签指针等场景中的正确用法。一、addr()是什么提取指针的地址部分addr()的文档定义非常精炼获取指针的地址部分Gets the address portion of the pointer。它定义在*const T与*mut T两个原始指针类型上是 [Strict Provenance][crate::ptr#strict-provenance] API 家族的成员。// *const T 与 *mut T 上签名一致 pub fn addr(self) - usize理解addr()的关键在于它与self as usize的本质区别provenance 被丢弃指针的出处信息被完全丢弃provenance 不被暴露与as usize转换不同它不会产生暴露出处exposed provenance的副作用。这意味着把addr()返回的地址再转回指针得到的将是一个[无出处的指针][without_provenance]即ptr::without_provenance(addr)的结果对这样的指针进行解引用属于未定义行为UB。注意addr()提取的地址是可以通过usize完整表示的地址部分但它不能被当作usize参与指针→整数→指针的往返转换——这正是文档强调address而非integer的原因。二、如何恢复丢失的信息with_addr与map_addraddr()丢弃 provenance 后如果你还需要一个可解引用的指针文档给出了两条正确路径[with_addr][pointer::with_addr]以self的 provenance 创建一个给定地址的新指针[map_addr][pointer::map_addr]with_addr的便捷封装接受一个FnOnce(usize) - usize映射函数。源码library/core/src/ptr/const_ptr.rs揭示了with_addr的实现思路——它其实是用wrapping_byte_offset完成的pub fn with_addr(self, addr: usize) - Self { // 注释这里本应使用 intrinsic 以避免任何算术运算 // 但目前可以用 wrapping_offset 实现它能保留指针的 provenance。 let self_addr self.addr() as isize; let dest_addr addr as isize; let offset dest_addr.wrapping_sub(self_addr); self.wrapping_byte_offset(offset) } pub fn map_addr(self, f: impl FnOnce(usize) - usize) - Self { self.with_addr(f(self.addr())) }也就是说with_addr等价于用wrapping_offset把self偏移到给定地址因此它继承了wrapping_offset的全部能力与限制只要self仍指向你关心的那个分配allocation你就能先降级到地址表示做任意位运算技巧再立即恢复成一个保留 provenance的指针。典型实战标签指针Tagged Pointerlibrary/core/src/ptr/mod.rs 给出了 Strict Provenance 下做标签指针的完整示例把标志位打包进地址的低位读取前再解包。核心模式就是用map_addr打标签 / 解标签用addr()读地址// 一个要打包进指针的标志位 static HAS_DATA: usize 0x1; static FLAG_MASK: usize !HAS_DATA; // 我们的值必须有足够对齐以留出空闲的最低位 let my_precious_data: u32 17; assert!(align_of::u32() 1); // 创建一个标签指针 let ptr my_precious_data as *const u32; let tagged ptr.map_addr(|addr| addr | HAS_DATA); // 检查标志位 if tagged.addr() HAS_DATA ! 0 { // 解标签并读取指针 let data unsafe { *tagged.map_addr(|addr| addr FLAG_MASK) }; assert_eq!(data, 17); } else { unreachable!() }这个例子的正确性完全建立在指针始终以指针形态存在、从不退化为usize之上。文档还特别提醒如果此前你在并发数据结构里用AtomicUsize保存指针应当改用AtomicPtr。三、addr()的源码实现一次不暴露出处的 transmuteaddr()的实现位于 library/core/src/ptr/const_ptr.rs*const T与 library/core/src/ptr/mut_ptr.rs*mut T二者完全对称#[doc include_str!(./docs/addr.md)] #[must_use] #[inline(always)] #[expect(clippy::transmutes_expressible_as_ptr_casts, reason implements pointer cast)] #[stable(feature strict_provenance, since 1.84.0)] pub fn addr(self) - usize { // 指针到整数的 transmute 目前恰好具有正确的语义 // 它返回地址而不暴露 provenance。 // 注意这*不是*关于 transmute 语义的稳定保证 // 它依赖于 sysroot crate 的特殊地位。 // SAFETY: 指针到整数的 transmute 是合法的如果你能接受丢失 provenance。 unsafe { mem::transmute(self.cast::()()) } }这里有三个值得注意的实现细节文档通过include_str!内嵌#[doc include_str!(./docs/addr.md)]说明 addr.md 正是被直接嵌入到addr()方法文档中的源文件——你所读到的每个字都是标准库 API 文档的一部分。实现方式是指针→整数transmute这是目前能表达取地址但不暴露 provenance的唯一手段。源码注释明确警告这不是关于 transmute 语义的稳定保证它依赖 sysroot crate 的特殊地位。稳定版本#[stable(feature strict_provenance, since 1.84.0)]表明该方法自 Rust 1.84 起稳定可用同时带有#[must_use]返回值不应被丢弃。变体NonNull::addr()对于NonNullTaddr()的返回类型是NonZerousize而非usizelibrary/core/src/ptr/non_null.rspub fn addr(self) - NonZerousize { // SAFETY: 指针由类型保证非空意味着地址必然非零。 unsafe { NonZero::new_unchecked(self.as_ptr().addr()) } }对应地NonNull上的with_addr/map_addr也以NonZerousize为地址类型library/core/src/ptr/non_null.rs从而在整个 API 面上维持非空的不变量。四、addr()vsexpose_provenance()Strict 与 Exposed 的分野Rust 为指针↔整数提供了两套并行的处理方式理解它们的差异是正确选用 API 的前提library/core/src/ptr/mod.rs对比维度addr()Strict Provenanceexpose_provenance()Exposed Provenance语义仅提取地址provenance 被丢弃提取地址同时把 provenance 加入已暴露清单等价操作不等价于任何as转换完全等价于self as usize逆向恢复用with_addr/map_addr需保留一个带出处指针用with_exposed_provenance可从纯地址重建工具与平台兼容兼容 Miri、CHERI 等检查工具在 Miri/CHERI 下不可用或受限规范完善度有清晰的、可形式化的语义语义基础薄弱无法提供哪个出处的保证源码实现library/core/src/ptr/const_ptr.rs可以清楚地看到二者差异pub fn expose_provenance(self) - usize { self.cast::()() as usize }expose_provenance只是普通的as usize转换而addr()刻意使用 transmute 并注释thisisthe replacement——addr()就是官方为 Strict Provenance 代码准备的as usize替代品。何时必须退而求其次文档明确指出了一个无法用 Strict Provenance 处理的场景从纯地址凭空构造一个合法指针例如裸机baremetal代码访问固定地址的 MMIO内存映射 I/O寄存器。此时没有保留出处的指针可用只能使用expose_provenancewith_exposed_provenance或as转换但代价是代码可移植性下降无法通过 Miri 等 Rust 内存模型合规检查工具的验证。with_exposed_provenance的实现library/core/src/ptr/mod.rs 附近说明其完全等价于addr as *const T返回指针的出处是某个先前被暴露过的指针的出处具体是哪个由编译器自行决定无法保证。五、平台差异地址字节数并不总等于指针字节数文档还专门说明了平台相关行为大多数平台指针的所有字节都用于描述地址因此addr()返回值的字节表示与原始指针完全相同需要在指针中存储额外信息的平台典型如 CHERI 架构的能力指针、某些 fat pointer 场景addr()会执行一次表示形式转换产出一个只含地址部分的值其具体含义由平台自行定义。这意味着在编写可移植代码时不能假设地址 指针位模式而应始终把addr()的返回值当作一个纯粹的usize地址来对待。同理expose_provenance在需要额外信息的平台上通常不可用因为支撑with_exposed_provenance工作的 expose 副作用在这些平台上一般不成立。六、决策指南我该用哪个 API结合 addr.md 与 mod.rs 的Using Strict Provenance小节可以给出如下决策路径只是读地址比较、打印、做算术用addr()。它明确承诺返回值不参与指针→整数→指针往返未来编译器可以据此对as指针转换施加 lint 审计。需要改地址但保留出处保留一个指向原分配的指针用with_addr/map_addr完成地址改写——这是标签指针、对齐技巧等场景的标准姿势。需要从纯整数重建合法指针如 MMIO只能用expose_provenance/with_exposed_provenance并明确接受其规范上的不确定性。无出处指针当哨兵值可用ptr::without_provenance(addr)/ptr::dangling::T()构造这类指针只能用于零大小访问或哨兵用途library/core/src/ptr/mod.rs。最后回到文档的结语如果代码中无法保留一个带所需出处的指针说明 Strict Provenance 可能不适合你此时使用指针整数转换或expose_provenance系列是合理退路但请记住——这会让代码更不易移植也更难被检查 Rust 内存模型合规性的工具所接受。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考