rust语言std::ptr

std::ptr 模块是 Rust 标准库中处理原始指针(raw pointers)和底层内存操作的核心模块。它提供了访问和操作内存的功能,这些功能通常绕过了 Rust 的借用检查器,因此大多数函数都需要在 unsafe 块中使用
对于日常编程,你几乎不需要使用 std::ptr。它主要供需要底层控制的场景使用,例如:
  • 编写需要高性能、精细内存布局的代码。
  • 构建自定义的智能指针或数据结构(如 Box<T>Vec<T> 的内部实现)。
  • 与 C 语言进行外部函数接口(FFI)交互。
关键类型:裸指针(Raw Pointers)
std::ptr 模块操作的核心类型是裸指针
  • *const T:不可变裸指针(“指向 T 的常量指针”)。
  • *mut T:可变裸指针(“指向 T 的可变指针”)。
std::ptr 中的重要函数
以下是一些常用且需要 unsafe 块的函数: 
1. std::ptr::read() 和 std::ptr::write()
这些函数允许你在不触发 Rust 的所有权和 Drop 语义的情况下读取和写入内存地址
  • read(src: *const T) -> T: 从一个指针指向的内存位置按位复制出值,并返回该值的所有权。它将原始位置留在一个未初始化的状态。如果 T 是一个拥有资源的类型(如 String),必须确保原始位置不会被再次使用或释放,否则会导致双重释放(double-free)错误。
  • write(dst: *mut T, src: T): 将一个值按位写入指定的内存位置,覆盖原有的数据而不先调用原有数据的析构函数(Drop 方法)。这对于初始化未初始化的内存非常有用。
2. std::ptr::copy() 和 std::ptr::copy_nonoverlapping()
类似于 C 语言中的 memcpy 和 memmove。它们用于在两个内存区域之间高效地复制原始字节。
  • copy(src: *const T, dst: *mut T, count: usize): 从源地址复制指定数量的字节到目标地址。源和目标区域可以重叠。
  • copy_nonoverlapping(src: *const T, dst: *mut T, count: usize): 效率更高,但要求源和目标区域绝对不能重叠。如果重叠,会导致未定义行为(Undefined Behavior, UB)。
3. std::ptr::null() 和 std::ptr::null_mut()
用于创建空指针(NULL 指针)。
  • null<T>() -> *const T: 创建一个不可变空指针。
  • null_mut<T>() -> *mut T: 创建一个可变空指针。
use std::ptr;

unsafe {
    let mut p_mut: *mut i32 = ptr::null_mut();
    // ... 在使用 p_mut 之前必须确保它不是 null 且指向有效内存 ...
    if p_mut.is_null() {
        // ...
    }
}
读到这里你也许会有个疑问,它俩的区别是什么呢?
需要特别注意的是指针类型变量的可变性这两个概念的区别,Rust 将“指针的权限”和“变量的权限”完全分开了

指针类型 (*const vs *mut):

    • 控制你是否有权修改指向的目标内存(类似于 C++ 的 const T*)。

变量声明 (let vs let mut):

    • 控制你是否有权修改指针变量存的地址(类似于 C++ 的 T* const)。
如果你想要 C++ 中的 const T* const(既不能改指向,也不能改内容):
在 Rust 中就是:
 
let p: *const T = ...; // let 锁死了地址,*const 锁死了内容
在 C++ 中,const 的位置决定了它是约束“内容”还是“地址”。但在 Rust 中,“变量本身是否可变”是由 let 关键字统一管理的,这使得语法更加解耦且清晰。在 Rust 中,指针是否能重新指向新地址,取决于承载指针的变量是否被声明为 let mut,而不是指针本身是 null 还是 null_mut
情况 A:使用 let 声明(不可变绑定)
无论你用哪种 null,变量本身都不能被重新赋值。
let p = std::ptr::null::<i32>();
// p = &x as *const i32; // 错误!因为变量 p 本身不是 mut 的 
情况 B:使用 let mut 声明(可变绑定)
无论初始值是 null() 还是 null_mut(),你都可以将变量重新指向另一个地址。
let mut p = std::ptr::null::<i32>(); 
let x = 10;
p = &x as *const i32; // 合法!变量 p 可以重新指向新地址
既然都能重新指向,为什么还要分两种?
这两者的区别不在于“变量能否换地址”,而在于通过这个指针对内存进行写操作的权限”:
  • *const T (null):类似于 C/C++ 中的 const T*。你通常不能通过它来修改所指向的数据(除非经过强转)。它在语义上承诺了“只读”。
  • *mut T (null_mut):类似于 C/C++ 中的 T*。你可以通过它来修改所指向的数据
实际代码示例
use std::ptr;

fn main() {
    // 1. 创建一个可变变量来持有空指针
    let mut p_const: *const i32 = ptr::null();
    let mut p_mut: *mut i32 = ptr::null_mut();

    let mut data = 42;

    // 2. 重新指向新地址(两者都可以)
    p_const = &data as *const i32;
    p_mut = &mut data as *mut i32;

    // 3. 解引用并修改数据(只有 *mut 可以直接做)
    unsafe {
        // *p_const = 100; // 错误:无法通过 *const 指针修改数据
        *p_mut = 100;      // 合法:通过 *mut 指针修改了 data
    }
    
    println!("{}", data); // 输出 100
}
如果看了上面的说明还是一头雾水,那么就看一下下面的表格总结吧,会有一种醍醐灌顶的参透玄机。
在 Rust 语言中,指针的语法结构与 C/C++ 的对应逻辑如下:
C/C++ 语法Rust 语义描述Rust 对应语法
const T* 指向常量的指针(指针指向的内容不可变) *const T
T* const 常量指针(指针变量本身地址不可变) let p: *const T 或 let p: *mut T
const T* const 指向常量的常量指针(双重不可变) let p: *const T
总结
std::ptr 模块提供了绕过 Rust 安全抽象的能力,让你能像 C 语言一样直接操作内存。使用它需要深入理解内存布局、所有权规则以及 unsafe Rust 的契约,因为编译器不再为你提供安全保证。
参考资料:
posted @ 2025-11-28 18:20  PKICA  阅读(17)  评论(0)    收藏  举报