【Rust 语言编程知识与应用:裸指针和Unsafe Rust详解】
文章目录
摘要: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):包含额外元数据(如
&str的ptr + len、dyn Trait的ptr + vtable)。 - 胖指针(Wide Pointer):同宽指针。
Rust 指针全景:
- 原生指针:
*const T/*mut T - 引用:
&T/&mut T - 智能指针:
Box<T>/Rc<T>/Arc<T> - 函数指针 /
NonNull<T>
注意事项与最佳实践:
- 本章重点:裸指针 +
unsafe。 - 深度提示:裸指针无生命周期、无自动 Drop、无类型安全检查。
- 最佳实践:优先用引用/智能指针;裸指针仅用于 FFI、自定义结构、极致性能场景。
二、裸指针概念与创建方式
专业名词释义:
- 裸指针:可指向任意地址、可为空、无自动清理、无类型安全、在
safe代码创建但必须unsafe解引用。
创建方式:
- 强制转换引用:
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;
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)); }
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);
- 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 数组)。 - 深度提示:
offset按size_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):
- Double free:
drop(x)后再p.as_ref()。 - 数据竞争:多线程同时
as_mut()。 - 越界:
p.as_ptr().offset(100)。
注意事项与最佳实践:
NonNull仍不保证有效/对齐/生命周期,仍需开发者负责。- 深度提示:用于自定义结构、FFI、内存管理。
- 最佳实践:封装
NonNull+ RAII 实现安全 API。
八、unsafe 关键字基本用法
使用场景:
unsafe fn(额外安全条件需调用者满足)。unsafe { ... }块(内部使用 unsafe 代码,但上下文已满足条件)。unsafe trait(impl 时需满足额外条件)。unsafe impl(声明满足 unsafe trait)。
extern “C” 函数:隐式 unsafe(需调用者判断线程安全、异常等)。
注意事项与最佳实践:
unsafe= “我已手动验证安全契约”。- 深度提示:
unsafe trait如GlobalAlloc、Send/Sync的 unsafe impl。 - 最佳实践:
unsafe块尽量小;写清楚// SAFETY:注释。
本章小结 + 进阶练习
学完本章你应该能做到:
- 熟练创建/使用
*const T/*mut T与NonNull - 掌握
offset/add/copy/write/read/replace/swap安全条件 - 理解
unsafe函数/块/trait/impl 的语义 - 严格遵守对齐 + 非空 + 有效性规则
进阶练习(建议立刻敲代码 + 测试 UB):
- 用
NonNull+ptr::write实现简易Box(手动分配/释放)。 - 实现无锁环形缓冲区(
offset+copy_nonoverlapping)。 - FFI 调用 C 函数,返回
*mut T,用NonNull封装安全 API。 - 用
ptr::swap+MaybeUninit实现零拷贝交换(验证对齐)。 - 手动实现
Vec的push(ptr::write+offset+ 容量检查)。 - 对比
unsafevssafe版本性能(Cargo bench),并列出所有 UB 场景。
裸指针 + unsafe + NonNull = Rust 底层终极武器。掌握“手动安全契约”思维,你就能安全进入 FFI、无锁算法、自定义分配器等领域,真正实现“零成本 + 零妥协”的系统级编程!
(完)
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)