时间:2025-03-10 17:47
人气:
作者:admin
指针在前面的篇幅中已经介绍过许多,但主要是智能指针。
智能指针管理堆上的数据,并且受到rust的所有权和借用规则管理(注意,这里的所有权和借用有时候不同于最原始的那种)。
智能指针好歹能管着这些数据,但是rust中存在一些不能使用所有权管理的数据,它们需要利用原始指针来管理。
本文简要讨论原始指针(raw pointer)。
注:<<Rust程序设计语言>>翻译为裸指针,个人觉得不太贴却,那样容易让人联想到没有元数据的智能指针,其次“裸”并没有关联到raw的核心意思:
未经加工的;生的;未经处理的;真实的;原始的;寒冷的;自然状态的;未经训练的;未经分析的;工作生疏的;未烹制的;未煮的;红肿疼痛的;
在那本书中,并没有给出原始指针的定义,找了一圈,大体可以如下定义:
在代码上,如下定义一个原始指针:
注意:这里的✳号不是表示解引用,就是和C中的指针前的*差不多的意思。
以下是C语言中的指针定义示例:
int *ptr;
char *str = "Hello, World!";
如前定义,原始指针的主要作用是直接操作内存,同时超凡脱俗(不用管所有权和借用原则)的特性导致可以用于以下几种业务场景:
毫无疑问,即使使用rust编码,使用到原始指针的机会也不会很多,否则不如用C,C++之类的语言。
毫无疑问,现在对于原始指针以及内存操作并不熟悉,本文的例子基本上是来自<<rust程序设计语言>>,只不过部分稍作了一些调整。
此处示例主要关于:原始指针的创建和销毁
use std::ffi::CString; use std::os::raw::c_char; use std::slice; fn main() { //1.0 演示原始指针的创建和访问,以及可以同时拥有多个指向相同位置的原始指针(不论是可变还是不可变的原始指针) // 以及如何解引用原始指针 let mut num = 5; //这里必须定义为mut 。但也发现了rust编译器的一个问题:会错误体提示要移除这个mut,但其实不能移除。 let r1 = &num as *const i32; // 不可变原始指针 let r2 = &mut num as *mut i32; // 可变原始指针 println!("r1: {}, r2: {}", unsafe { *r1 }, unsafe { *r2 }); //让r2+1 unsafe { *r2 += 1; } println!("r1: {}, r2: {}", unsafe { *r1 }, unsafe { *r2 }); //2.0 创建指向任意内存地址的裸指针 // 此处代码需要注释掉,否则后续的代码不会执行。因为原始指针指向的内存地址是不合法的,所以会引发运行时错误。 //create_raw_pointer_from_address(); println!("---------------------------------------------------------------"); //3.0 使用Box创建原始指针 create_raw_pointer_use_box(); //4.0 如何创建一个空的原始指针 create_empty_raw_pointer(); //5.0 演示如果使用原始指针把一个数组切成两半,且每一半都是可变的切片 demo_split_slice_to_two_half(); } /** * 创建一个指向任意内存地址的原始指针 */ #[allow(dead_code)] fn create_raw_pointer_from_address() { let address = 0x012345usize; let r = address as *const i32; unsafe { println!("r: {}", *r); } } /** * 使用Box创建原始指针 */ fn create_raw_pointer_use_box() { //回忆下Box指针,我们知道Box指针是一个堆上分配的智能指针。 let boxed = Box::new(5); let five = *boxed; println!("five: {}", five); let brave = Box::new(String::from("Rust")); let raw_brave = Box::into_raw(brave); unsafe{ println!("raw_brave: {:?}", *raw_brave); } //现在需要手动释放内存,否则会造成内存泄露 println!("释放raw_brave"); unsafe{ drop(Box::from_raw(raw_brave)); } println!("释放raw_brave完成"); // unsafe{ // println!("raw_brave依然存在,但这个时候它应该是一个空的: {:?}", *raw_brave); // } } /** * 创建一个空的原始指针-利用std::ptr::null */ fn create_empty_raw_pointer() { let mut ptr = std::ptr::null::<i32>(); if ptr.is_null() { println!("指针ptr是空的"); } else { println!("指针ptr不是空的"); } let mut value = 5; unsafe { // 将指针指向一个有效的内存地址 ptr = &value as *const i32; // 读取指针的值 if !ptr.is_null() { println!("指针ptr现在的值是: {}", *ptr); } else { println!("指针ptr现在是空的"); } } } /** * 演示如果使用原始指针把一个数组切成两半,且每一半都是可变的切片 */ fn demo_split_slice_to_two_half() { let mut scores=[10,20,30,40,50]; println!("原始scores: {:?}", scores); let (left, right) = unsafe_slice(&mut scores, 3); println!("scores-left: {:?}, scores-right: {:?}", left, right); //现在改改左边第一个,看是不是发生了效果 left[0] = 1024; right[0] = 1975; println!("修改后scores: {:?}", scores); //事实证明了这点:左边改了,所以可变切片可以的 let mut poems=["独怜幽草涧边生","上有黄丽深树鸣","春潮带雨晚来急"]; let (left, right) = unsafe_slice(&mut poems, 2); println!("left: {:?}, right: {:?}", left, right); } /** * 使用原始指针创建切片 * 这里有非常关键的说明:(来自书本) * Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同部分:它只知道我们借用了同一个 slice 两次。 * 本质上借用 slice 的不同部分是可以的,因为结果两个 slice 不会重叠,不过 Rust 还没有智能到能够理解这些 * * 这一段至少说明了:rust的编译器也不是万能的,要是万能就太慢了。 * 在rust书中,此例子属于:创建不安全代码的安全抽象 。 * 即把不安全的操作封装起来,对外提供安全的接口(函数) * @param values 实际是一个数组的完整切片 * @param mid 切片中间的索引位置 */ fn unsafe_slice<T>(values: &mut [T], mid: usize)-> (&mut [T], &mut [T]) { //let mut data = [1, 2, 3]; //let ptr = &data as *const _; let len = values.len(); let ptr = values.as_mut_ptr(); // 切片转为原始指针,且指向切片的起始位置。 //方法from_raw_parts_mut用于从原始指针和长度创建一个可变切片。 //注意:这里的add方法,是原始指针的一个方法,用于计算偏移后的新地址。 //如果切片中保存的是汉字,那么应该加多少了? unsafe { ( slice::from_raw_parts_mut(ptr, mid), slice::from_raw_parts_mut(ptr.add(mid), len - mid), ) } }
输出如下:

有几点值得说:
本文来自博客园,作者:正在战斗中,转载请注明原文链接:https://www.cnblogs.com/lzfhope/p/18759133