跳至内容
张叶安的小站
用户工具
登录
站点工具
搜索
工具
显示页面
过去修订
反向链接
最近更改
媒体管理器
网站地图
登录
>
最近更改
媒体管理器
网站地图
您的足迹:
rust:第十七章unsaferust
本页面只读。您可以查看源文件,但不能更改它。如果您觉得这是系统错误,请联系管理员。
====== 第十七章 Unsafe Rust ====== Rust的安全保证是其最大的卖点,但有时这些保证会阻止你完成某些任务。Unsafe Rust允许你执行一些编译器无法保证内存安全的操作。本章将详细介绍何时以及如何使用Unsafe Rust。 ===== 1. 为什么要使用Unsafe ===== ==== 1.1 Safe Rust的限制 ==== Rust编译器强制执行许多安全规则: * 禁止空指针解引用 * 禁止数据竞争 * 禁止缓冲区溢出 * 禁止使用未初始化内存 但在某些情况下,这些限制过于严格: * 与C/C++代码交互(FFI) * 实现底层数据结构(如Vec、HashMap) * 直接使用硬件 * 极致性能优化 ==== 1.2 Unsafe Rust的能力 ==== Unsafe Rust允许你执行五种操作: * 解引用原始指针 * 调用unsafe函数或方法 * 访问或修改可变静态变量 * 实现unsafe trait * 访问union的字段 **重要**:unsafe并不意味着代码一定危险或内存不安全。它只是将保证安全的责任从编译器转移到了程序员。 ===== 2. 原始指针(Raw Pointers) ===== ==== 2.1 原始指针类型 ==== Rust有两种原始指针: * ***const T**:不可变原始指针,类似于&T * ***mut T**:可变原始指针,类似于&mut T <code rust> fn main() { let mut num = 5; // 创建原始指针 let r1 = &num as *const i32; // 不可变原始指针 let r2 = &mut num as *mut i32; // 可变原始指针 // 注意:这里在safe代码中创建了原始指针是允许的 // 但解引用它们需要unsafe块 } </code> ==== 2.2 解引用原始指针 ==== <code rust> fn main() { let mut num = 5; let r1 = &num as *const i32; let r2 = &mut num as *mut i32; unsafe { println!("r1 is: {}", *r1); // 5 println!("r2 is: {}", *r2); // 5 *r2 = 10; println!("修改后 r1 is: {}", *r1); // 10 } } </code> ==== 2.3 原始指针与引用的区别 ==== | 特性 | 原始指针 | 引用 | | 允许空值 | 是 | 否 | | 允许多个可变指针 | 是 | 否 | | 不保证指向有效内存 | 是 | 否 | | 自动释放 | 否 | 是 | | 实现Send/Sync | 是 | 有条件 | ==== 2.4 原始指针的使用场景 ==== <code rust> // 与C代码交互 extern "C" { fn abs(input: i32) -> i32; } fn main() { unsafe { println!("C abs(-3) = {}", abs(-3)); } } </code> <code rust> // 实现自己的智能指针 struct MyBox<T> { ptr: *mut T, } impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { let ptr = Box::into_raw(Box::new(x)); MyBox { ptr } } fn get(&self) -> &T { unsafe { &*self.ptr } } fn get_mut(&mut self) -> &mut T { unsafe { &mut *self.ptr } } } impl<T> Drop for MyBox<T> { fn drop(&mut self) { unsafe { Box::from_raw(self.ptr); // 重新获取所有权并释放 } } } fn main() { let mut b = MyBox::new(5); println!("{}", b.get()); *b.get_mut() = 10; println!("{}", b.get()); } </code> ===== 3. Unsafe函数和方法 ===== ==== 3.1 声明unsafe函数 ==== <code rust> unsafe fn dangerous() { println!("这是一个unsafe函数"); } fn main() { unsafe { dangerous(); // 必须在unsafe块中调用 } } </code> ==== 3.2 创建safe的抽象 ==== 最佳实践是将unsafe代码封装在safe的API中: <code rust> use std::slice; // 对外暴露safe接口 fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { let len = slice.len(); assert!(mid <= len); // 内部使用unsafe unsafe { ( slice::from_raw_parts_mut(slice.as_mut_ptr(), mid), slice::from_raw_parts_mut(slice.as_mut_ptr().add(mid), len - mid), ) } } fn main() { let mut v = vec![1, 2, 3, 4, 5]; let (left, right) = split_at_mut(&mut v, 2); println!("left: {:?}, right: {:?}", left, right); } </code> ==== 3.3 使用extern函数 ===== **调用C代码**: <code rust> // 声明C函数 #[link(name = "c")] extern "C" { fn printf(format: *const u8, ...) -> i32; fn strlen(s: *const u8) -> usize; } fn main() { unsafe { let msg = b"Hello, C!\0"; printf(msg.as_ptr() as *const u8); let s = b"Rust\0"; let len = strlen(s.as_ptr()); println!("长度: {}", len); } } </code> **导出Rust函数给C**: <code rust> #[no_mangle] pub extern "C" fn add(a: i32, b: i32) -> i32 { a + b } // 使用panic时防止未定义行为 #[no_mangle] pub extern "C" fn safe_add(a: i32, b: i32) -> i32 { std::panic::catch_unwind(|| { a + b }).unwrap_or(0) } </code> ===== 4. 可变静态变量 ===== ==== 4.1 全局可变状态 ==== <code rust> static mut COUNTER: u32 = 0; fn add_to_count(inc: u32) { unsafe { COUNTER += inc; } } fn main() { add_to_count(3); unsafe { println!("COUNTER: {}", COUNTER); } } </code> **警告**:可变静态变量不是线程安全的,多线程访问会导致数据竞争。 ==== 4.2 使用同步原语替代 ==== <code rust> use std::sync::atomic::{AtomicU32, Ordering}; static COUNTER: AtomicU32 = AtomicU32::new(0); fn add_to_count(inc: u32) { COUNTER.fetch_add(inc, Ordering::SeqCst); } fn main() { add_to_count(3); println!("COUNTER: {}", COUNTER.load(Ordering::SeqCst)); } </code> ===== 5. Unsafe Trait ===== ==== 5.1 声明unsafe trait ==== 当trait的某些不变量编译器无法验证时,使用unsafe trait。 <code rust> unsafe trait Foo { // 方法定义 } unsafe impl Foo for i32 { // 实现 } </code> ==== 5.2 标准库中的unsafe trait ==== **Send和Sync**: <code rust> // 实现Send表示类型可以安全地在线程间转移所有权 unsafe impl Send for MyType {} // 实现Sync表示类型可以安全地被多个线程共享引用 unsafe impl Sync for MyType {} </code> **GlobalAlloc**: <code rust> use std::alloc::{GlobalAlloc, Layout, System}; struct MyAllocator; unsafe impl GlobalAlloc for MyAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { System.alloc(layout) } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { System.dealloc(ptr, layout) } } #[global_allocator] static ALLOCATOR: MyAllocator = MyAllocator; </code> ===== 6. Union类型 ===== Union类似于C的union,所有字段共享同一内存位置。 <code rust> #[repr(C)] union MyUnion { i: i32, f: f32, } fn main() { let mut u = MyUnion { i: 1 }; unsafe { println!("作为i32: {}", u.i); u.f = 3.14; println!("作为f32: {}", u.f); } } </code> ===== 7. 实现不安全的数据结构 ===== ==== 7.1 实现自己的Vec ==== <code rust> use std::alloc::{self, Layout}; use std::ptr::{self, NonNull}; struct MyVec<T> { ptr: NonNull<T>, len: usize, capacity: usize, } impl<T> MyVec<T> { fn new() -> Self { Self { ptr: NonNull::dangling(), len: 0, capacity: 0, } } fn push(&mut self, elem: T) { if self.len == self.capacity { self.grow(); } unsafe { ptr::write(self.ptr.as_ptr().add(self.len), elem); } self.len += 1; } fn pop(&mut self) -> Option<T> { if self.len == 0 { return None; } self.len -= 1; unsafe { Some(ptr::read(self.ptr.as_ptr().add(self.len))) } } fn grow(&mut self) { let new_capacity = if self.capacity == 0 { 1 } else { self.capacity * 2 }; let new_layout = Layout::array::<T>(new_capacity).unwrap(); let new_ptr = if self.capacity == 0 { unsafe { alloc::alloc(new_layout) as *mut T } } else { let old_layout = Layout::array::<T>(self.capacity).unwrap(); unsafe { alloc::realloc( self.ptr.as_ptr() as *mut u8, old_layout, new_layout.size(), ) as *mut T } }; self.ptr = NonNull::new(new_ptr).expect("分配失败"); self.capacity = new_capacity; } fn len(&self) -> usize { self.len } fn capacity(&self) -> usize { self.capacity } fn get(&self, index: usize) -> Option<&T> { if index >= self.len { return None; } unsafe { Some(&*self.ptr.as_ptr().add(index)) } } } impl<T> Drop for MyVec<T> { fn drop(&mut self) { if self.capacity == 0 { return; } // 逐个drop元素 for i in 0..self.len { unsafe { ptr::drop_in_place(self.ptr.as_ptr().add(i)); } } // 释放内存 let layout = Layout::array::<T>(self.capacity).unwrap(); unsafe { alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout); } } } fn main() { let mut vec = MyVec::new(); vec.push(1); vec.push(2); vec.push(3); println!("长度: {}, 容量: {}", vec.len(), vec.capacity()); while let Some(val) = vec.pop() { println!("弹出: {}", val); } } </code> ==== 7.2 实现双向链表(简化版) ==== <code rust> use std::ptr::NonNull; struct Node<T> { elem: T, next: Option<NonNull<Node<T>>>, prev: Option<NonNull<Node<T>>>, } pub struct LinkedList<T> { head: Option<NonNull<Node<T>>>, tail: Option<NonNull<Node<T>>>, len: usize, } impl<T> LinkedList<T> { pub fn new() -> Self { Self { head: None, tail: None, len: 0, } } pub fn push_front(&mut self, elem: T) { let new_node = Box::new(Node { elem, next: self.head, prev: None, }); let new_node = NonNull::new(Box::into_raw(new_node)); match self.head { Some(mut head) => unsafe { head.as_mut().prev = new_node; }, None => self.tail = new_node, } self.head = new_node; self.len += 1; } pub fn pop_front(&mut self) -> Option<T> { self.head.map(|node| unsafe { let node = Box::from_raw(node.as_ptr()); self.head = node.next; match self.head { Some(mut head) => head.as_mut().prev = None, None => self.tail = None, } self.len -= 1; node.elem }) } } impl<T> Drop for LinkedList<T> { fn drop(&mut self) { while self.pop_front().is_some() {} } } fn main() { let mut list = LinkedList::new(); list.push_front(1); list.push_front(2); list.push_front(3); while let Some(val) = list.pop_front() { println!("{}", val); } } </code> ===== 8. 内存布局与repr属性 ===== ==== 8.1 repr(C) ==== 与C兼容的内存布局: <code rust> #[repr(C)] struct Point { x: f64, y: f64, } #[repr(C)] struct Rectangle { top_left: Point, bottom_right: Point, } </code> ==== 8.2 repr(packed) ==== 去除填充字节,但可能降低性能: <code rust> #[repr(packed)] struct PackedStruct { a: u8, b: u32, // 通常会有3字节填充,packed去除了填充 } </code> ==== 8.3 repr(align) ==== 指定对齐方式: <code rust> #[repr(align(64))] struct CacheLineAligned { data: [u8; 64], } fn main() { assert_eq!(std::mem::align_of::<CacheLineAligned>(), 64); } </code> ===== 9. FFI(外部函数接口) ===== ==== 9.1 与C库交互 ==== <code rust> // 绑定到C标准库 #[link(name = "c")] extern "C" { fn malloc(size: usize) -> *mut c_void; fn free(ptr: *mut c_void); fn memset(ptr: *mut c_void, value: c_int, size: usize) -> *mut c_void; } use std::os::raw::{c_void, c_int}; fn main() { unsafe { let ptr = malloc(1024); if ptr.is_null() { panic!("内存分配失败"); } memset(ptr, 0, 1024); free(ptr); } } </code> ==== 9.2 使用bindgen自动生成绑定 ==== 安装bindgen: <code bash> cargo install bindgen-cli </code> 生成绑定: <code bash> bindgen /usr/include/some_header.h -o src/bindings.rs </code> ==== 9.3 回调函数 ==== C调用Rust回调: <code rust> // Rust代码 pub extern "C" fn rust_callback(data: i32) { println!("从C收到数据: {}", data); } // 声明C函数 #[link(name = "some_c_lib")] extern "C" { fn register_callback(cb: extern "C" fn(i32)); fn trigger_callback(); } fn main() { unsafe { register_callback(rust_callback); trigger_callback(); } } </code> ==== 9.4 处理字符串 ==== <code rust> use std::ffi::{CStr, CString}; use std::os::raw::c_char; fn rust_string_to_c(s: &str) -> CString { CString::new(s).expect("字符串包含空字节") } unsafe fn c_string_to_rust(ptr: *const c_char) -> String { CStr::from_ptr(ptr) .to_str() .expect("无效UTF-8") .to_string() } fn main() { let c_string = rust_string_to_c("Hello"); println!("C字符串: {:?}", c_string); // 假设从C收到了一个字符串指针 // let rust_str = unsafe { c_string_to_rust(c_ptr) }; } </code> ===== 10. 内联汇编 ===== Rust支持内联汇编(nightly版本): <code rust> #![feature(asm)] fn main() { let mut x: u64 = 4; unsafe { asm!( "add {0}, {1}", inout(reg) x, in(reg) 5, ); } assert_eq!(x, 9); } </code> 稳定版本可以使用外部汇编文件或cc crate: <code rust> // build.rs fn main() { cc::Build::new() .file("src/asm_code.S") .compile("asm_code"); } </code> ===== 11. 未定义行为(UB) ===== ==== 11.1 常见的UB来源 ==== * 解引用空指针或悬空指针 * 读取未初始化内存 * 违反借用规则(同时拥有可变和不可变引用) * 使用某些类型的错误值(如bool不是0或1) * 数据竞争 * 执行不返回的函数后继续使用值 ==== 11.2 避免UB的最佳实践 ==== <code rust> // 1. 始终检查指针有效性 unsafe fn safe_deref(ptr: *const i32) -> Option<i32> { if ptr.is_null() { return None; } Some(*ptr) } // 2. 使用NonNull确保非空 use std::ptr::NonNull; struct SafePtr<T> { ptr: NonNull<T>, } // 3. 使用MaybeUninit处理未初始化内存 use std::mem::MaybeUninit; fn create_array<T, F>(mut f: F) -> [T; 100] where F: FnMut(usize) -> T, { let mut arr: [MaybeUninit<T>; 100] = unsafe { MaybeUninit::uninit().assume_init() }; for i in 0..100 { arr[i] = MaybeUninit::new(f(i)); } unsafe { std::mem::transmute::<_, [T; 100]>(arr) } } </code> ===== 12. Miri:检测UB的工具 ===== Miri是一个Rust解释器,可以检测未定义行为: <code bash> rustup component add miri cargo miri test </code> <code rust> // 这段代码会被Miri检测到UB fn main() { let ptr = Box::into_raw(Box::new(1)); unsafe { // 错误:使用已经被释放的内存 drop(Box::from_raw(ptr)); println!("{}", *ptr); // UB! } } </code> ===== 13. 性能优化中的Unsafe ===== ==== 13.1 消除边界检查 ==== <code rust> fn sum_array(arr: &[i32]) -> i32 { let mut sum = 0; unsafe { // 我们知道索引是安全的 for i in 0..arr.len() { sum += *arr.get_unchecked(i); } } sum } </code> ==== 13.2 SIMD操作 ==== <code rust> #[cfg(target_arch = "x86_64")] use std::arch::x86_64::*; #[target_feature(enable = "avx2")] unsafe fn add_vectors_avx2(a: &[f32], b: &[f32], c: &mut [f32]) { let len = a.len(); let mut i = 0; while i + 8 <= len { let va = _mm256_loadu_ps(a.as_ptr().add(i)); let vb = _mm256_loadu_ps(b.as_ptr().add(i)); let vc = _mm256_add_ps(va, vb); _mm256_storeu_ps(c.as_mut_ptr().add(i), vc); i += 8; } // 处理剩余元素 for j in i..len { c[j] = a[j] + b[j]; } } fn main() { let a = vec![1.0f32; 16]; let b = vec![2.0f32; 16]; let mut c = vec![0.0f32; 16]; unsafe { add_vectors_avx2(&a, &b, &mut c); } println!("{:?}", c); } </code> ===== 14. 安全抽象设计原则 ===== ==== 14.1 最小化unsafe代码 ==== <code rust> // 不好:整个函数都是unsafe pub unsafe fn process_data(data: *mut u8, len: usize) { // ... } // 好:将unsafe封装在内部 pub fn process_data(data: &mut [u8]) { unsafe { // 只在必要时使用unsafe } } </code> ==== 14.2 文档化安全不变量 ==== <code rust> /// # Safety /// /// 调用者必须保证: /// - `ptr`指向有效的内存 /// - `ptr`对齐到T的对齐要求 /// - `ptr`指向的内存不会被同时访问 pub unsafe fn read_data<T>(ptr: *const T) -> T { ptr::read(ptr) } </code> ==== 14.3 使用assert!进行调试检查 ==== <code rust> pub unsafe fn read_unchecked<T>(slice: &[T], index: usize) -> &T { debug_assert!(index < slice.len(), "索引越界"); slice.get_unchecked(index) } </code> ===== 小结 ===== 本章深入探讨了Unsafe Rust: * **原始指针**:*const T和*mut T,无安全检查的指针 * **unsafe函数/块**:包含unsafe操作的代码块 * **FFI**:与C/C++代码交互 * **可变静态变量**:全局可变状态 * **unsafe trait**:如Send、Sync、GlobalAlloc * **Union**:共享内存布局的类型 使用Unsafe Rust的关键原则: * 尽可能保持unsafe代码量最小 * 用safe包装器封装unsafe实现 * 充分文档化安全不变量 * 使用Miri等工具检测UB * 让unsafe代码通过尽可能多的测试 Unsafe Rust是一把双刃剑,它赋予你极致的控制权和性能,但也要求你对内存模型和未定义行为有深入理解。合理使用unsafe,你可以在保持Rust安全保证的同时完成最底层的工作。
rust/第十七章unsaferust.txt
· 最后更改:
2026/02/03 22:33
由
127.0.0.1
页面工具
显示页面
过去修订
反向链接
回到顶部