摘要:Rust 裸指针(*const T / *mut T)是底层原生类型,可指向任意内存,但不自动检查有效性、空指针、对齐、生命周期。unsafe 关键字标记“需要额外安全条件”的函数、块、trait、impl。std::ptr 模块提供 offset/add/copy/write/read/replace/swap 等操作,NonNull<T> 提供非空协变安全抽象。文章深度对比裸指针与引用/C++、对齐要求、UB 场景(double free、数据竞争、越界),并给出 unsafe 使用规范,帮助你安全掌握“零开销底层控制 + 编译器无法验证的安全契约”,写出 FFI、自定义数据结构、无锁算法等高性能代码。(158 字)

一、指针分类(ptr 模块)

专业名词释义

  • 裸指针(Raw Pointer)*const T(不可变) / *mut T(可变),原生指针类型。
  • 宽指针(Fat Pointer):包含额外元数据(如 &strptr + lendyn Traitptr + vtable)。
  • 胖指针(Wide Pointer):同宽指针。

Rust 指针全景

  • 原生指针:*const T / *mut T
  • 引用:&T / &mut T
  • 智能指针:Box<T> / Rc<T> / Arc<T>
  • 函数指针 / NonNull<T>

注意事项与最佳实践

  • 本章重点:裸指针 + unsafe
  • 深度提示:裸指针无生命周期、无自动 Drop、无类型安全检查。
  • 最佳实践:优先用引用/智能指针;裸指针仅用于 FFI、自定义结构、极致性能场景。

二、裸指针概念与创建方式

专业名词释义

  • 裸指针:可指向任意地址、可为空、无自动清理、无类型安全、在 safe 代码创建但必须 unsafe 解引用。

创建方式

  1. 强制转换引用:
let my_num: i32 = 10;
let my_num_ptr: *const i32 = &my_num;
let mut my_speed: i32 = 88;
let my_speed_ptr: *mut i32 = &mut my_speed;
  1. Box::into_raw
let my_speed: Box<i32> = Box::new(88);
let raw: *mut i32 = Box::into_raw(my_speed);
unsafe { drop(Box::from_raw(raw)); }
  1. std::ptr::addr_of! 宏(支持 packed 结构):
#[repr(C, packed)] struct S { aligned: u8, unaligned: u32 }
let s = S::default();
let p = std::ptr::addr_of!(s.unaligned);
  1. C FFI(libc::malloc):
unsafe {
    let my_num: *mut i32 = libc::malloc(std::mem::size_of::<i32>()) as *mut i32;
    if my_num.is_null() { panic!(); }
    libc::free(my_num as *mut libc::c_void);
}

注意事项与最佳实践

  • 裸指针允许为空,解引用空指针 = UB。
  • 深度提示Box::into_raw 转移所有权,需手动 from_raw 回收。
  • 最佳实践:创建裸指针在 safe 代码,解引用永远 unsafe

三、裸指针解引用与指针偏移

专业名词释义

  • 解引用:必须在 unsafe 块中使用 *raw
  • offset / add:计算指针偏移(offset(isize) 可正负,add(usize) 仅向前)。

示例

let s: &str = "123";
let ptr: *const u8 = s.as_ptr();
unsafe {
    println!("{}", *ptr.offset(1) as char);  // '2'
    println!("{}", *ptr.offset(2) as char);  // '3'
}

安全条件(offset):

  • 起始与结果指针必须在同一分配对象内或超出一个字节末尾。
  • 偏移量(字节) ≤ isize::MAX
  • 不能依赖地址环绕(用 wrapping_offset)。

注意事项与最佳实践

  • 越界 offset = UB(示例 p.offset(6) 在 len=5 数组)。
  • 深度提示offsetsize_of::<T>() 计算字节偏移。
  • 最佳实践:优先 add(向前);边界检查自己保证。

四、裸指针对比引用 / C/C++

裸指针 vs 引用(表格对比):

特性 裸指针 引用
安全性 非安全(需 unsafe 解引用) 安全
可空性 可以为空 不能为空
生命周期
读写权限检查 编译器不检查 编译器检查
对齐要求
可指向任意地址
自动引用/解引用 不支持 支持
共享/可变借用规则 有(借用检查器)

裸指针 vs C/C++

  • Rust:不支持隐式类型转换、 *ptr = data 会调用 Drop(所有权相关);解引用必须 unsafe
  • C/C++:支持隐式转换、无特殊限制、*ptr = data 只调用赋值运算符。

注意事项与最佳实践

  • 裸指针无借用检查器保护,易数据竞争/悬垂指针。
  • 深度提示 *ptr = data 在 Rust 会 Drop 旧值(C++ 不)。
  • 最佳实践:能用引用绝不用裸指针。

五、裸指针安全抽象与使用规则

使用规则

  • 解引用前必须:非空 + 对齐 + 内存有效 + 整个期间有效。
  • 检查 is_null()
  • 多线程必须同步(防止数据竞争)。
  • 避免重复释放、悬垂指针、越界。
  • 尽可能少用裸指针,优先引用/智能指针。

注意事项与最佳实践

  • 深度提示:违反任何一条 = UB(未定义行为)。
  • 最佳实践:把裸指针封装进安全抽象(如 NonNull + RAII)。

六、std::ptr 常用方法(unsafe)

pointer::offset / add:已在上文详述。

ptr::copy(按位复制,类似 memmove):

unsafe { std::ptr::copy(src, dst, count); }
  • 安全条件:src/dst 有效 + 对齐;仅 T: Copy 时安全。

ptr::write(不 Drop 旧值,直接覆盖):

unsafe { std::ptr::write(dst, src); }
  • 语义:src 移动到 dst(所有权转移)。

ptr::read(不转移所有权读取):

unsafe { let y: T = std::ptr::read(src); }

ptr::replace / swap

  • replace:返回旧值 + 放入新值。
  • swap:交换两个位置。

注意事项与最佳实践

  • 所有方法均为 unsafe,必须满足对齐 + 初始化 + 有效性。
  • 深度提示write/read 不调用 Drop(可能泄漏资源)。
  • 最佳实践:能用 std::mem::replace/swap 就不要用 ptr 版本。

七、NonNull 类型(安全抽象)

专业名词释义

  • NonNull:非空协变可变裸指针,比 *mut T 类型更安全。

创建NonNull::new(ptr).unwrap()(自动检查非空)。

非法使用示例(导致 UB):

  1. Double free:drop(x) 后再 p.as_ref()
  2. 数据竞争:多线程同时 as_mut()
  3. 越界:p.as_ptr().offset(100)

注意事项与最佳实践

  • NonNull 仍不保证有效/对齐/生命周期,仍需开发者负责。
  • 深度提示:用于自定义结构、FFI、内存管理。
  • 最佳实践:封装 NonNull + RAII 实现安全 API。

八、unsafe 关键字基本用法

使用场景

  1. unsafe fn(额外安全条件需调用者满足)。
  2. unsafe { ... } 块(内部使用 unsafe 代码,但上下文已满足条件)。
  3. unsafe trait(impl 时需满足额外条件)。
  4. unsafe impl(声明满足 unsafe trait)。

extern “C” 函数:隐式 unsafe(需调用者判断线程安全、异常等)。

注意事项与最佳实践

  • unsafe = “我已手动验证安全契约”。
  • 深度提示unsafe traitGlobalAllocSend/Sync 的 unsafe impl。
  • 最佳实践unsafe 块尽量小;写清楚 // SAFETY: 注释。

本章小结 + 进阶练习

学完本章你应该能做到

  • 熟练创建/使用 *const T / *mut TNonNull
  • 掌握 offset/add/copy/write/read/replace/swap 安全条件
  • 理解 unsafe 函数/块/trait/impl 的语义
  • 严格遵守对齐 + 非空 + 有效性规则

进阶练习(建议立刻敲代码 + 测试 UB):

  1. NonNull + ptr::write 实现简易 Box(手动分配/释放)。
  2. 实现无锁环形缓冲区(offset + copy_nonoverlapping)。
  3. FFI 调用 C 函数,返回 *mut T,用 NonNull 封装安全 API。
  4. ptr::swap + MaybeUninit 实现零拷贝交换(验证对齐)。
  5. 手动实现 Vecpushptr::write + offset + 容量检查)。
  6. 对比 unsafe vs safe 版本性能(Cargo bench),并列出所有 UB 场景。

裸指针 + unsafe + NonNull = Rust 底层终极武器。掌握“手动安全契约”思维,你就能安全进入 FFI、无锁算法、自定义分配器等领域,真正实现“零成本 + 零妥协”的系统级编程!

(完)

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐