rust学习二十.1、不安全代码之原始指针(裸指针)

打印 上一主题 下一主题

主题 980|帖子 980|积分 2940

一、媒介

指针在前面的篇幅中已经先容过许多,但主要是智能指针。
智能指针管理堆上的数据,而且受到rust的所有权和借用规则管理(留意,这里的所有权和借用偶然候差别于最原始的那种)。
智能指针好歹能管着这些数据,但是rust中存在一些不能使用所有权管理的数据,它们必要利用原始指针来管理。
本文简要讨论原始指针(raw pointer)。
注:翻译为裸指针,个人觉得不太贴却,那样容易让人遐想到没有元数据的智能指针,其次“裸”并没有关联到raw的焦点意思:
未经加工的;生的;未经处理的;真实的;原始的;寒冷的;自然状态的;未经训练的;未经分析的;工作生疏的;未烹制的;未煮的;红肿疼痛的;
二、定义

在那本书中,并没有给出原始指针的定义,找了一圈,大体可以如下定义:

  • 原始指针雷同c,c++中的原始指针
  • 原始指针可以手动、直接操作内存,但是必要手动开释
  • 原始指针不遵守所谓所有权和借用原则,不包管操作安全性
在代码上,如下定义一个原始指针:

  • *const T  -- 不可变原始指针
  • *mut T    -- 可变原始指针
留意:这里的✳号不是表现解引用,就是和C中的指针前的*差不多的意思。
以下是C语言中的指针定义示例:
int *ptr;
char *str = "Hello, World!";
三、作用

如前定义,原始指针的主要作用是直接操作内存,同时超凡脱俗(不用管所有权和借用原则)的特性导致可以用于以下几种业务场景:

  • 调用其它语言的函数,目前主要是C语言
  • 执行低级别的内存操作-所谓低级别,就是不用管rust的一些规则,显得低级生猛
  • 实现高级数据结构和算法,这里主要指内存、并发数据、硬件接口等一些方面
  • 优化性能-用于极端环境下的性能优化。总之快是顶级快,只是不保障安全。这是少数环境下的要求
  • 提供编程机动性-简而言之就是rust规则之外的补充。 要只知道世界至少有两面,一个面是复合rust规则,一面是不复合的。
毫无疑问,纵然使用rust编码,使用到原始指针的机会也不会很多,否则不如用C,C++之类的语言。
四、示例

毫无疑问,现在对于原始指针以及内存操作并不熟悉,本文的例子基本上是来自,只不过部门稍作了一些调整。
此处示例主要关于:原始指针的创建和销毁
  1. use std::ffi::CString;
  2. use std::os::raw::c_char;
  3. use std::slice;
  4. fn main() {
  5.     //1.0 演示原始指针的创建和访问,以及可以同时拥有多个指向相同位置的原始指针(不论是可变还是不可变的原始指针)
  6.     // 以及如何解引用原始指针
  7.     let mut num = 5; //这里必须定义为mut 。但也发现了rust编译器的一个问题:会错误体提示要移除这个mut,但其实不能移除。
  8.     let r1 = &num as *const i32; // 不可变原始指针
  9.     let r2 = &mut num as *mut i32; // 可变原始指针
  10.     println!("r1: {}, r2: {}", unsafe { *r1 }, unsafe { *r2 });
  11.     //让r2+1
  12.     unsafe {
  13.         *r2 += 1;
  14.     }
  15.     println!("r1: {}, r2: {}", unsafe { *r1 }, unsafe { *r2 });
  16.     //2.0   创建指向任意内存地址的裸指针
  17.     // 此处代码需要注释掉,否则后续的代码不会执行。因为原始指针指向的内存地址是不合法的,所以会引发运行时错误。
  18.     //create_raw_pointer_from_address();
  19.     println!("---------------------------------------------------------------");
  20.     //3.0 使用Box创建原始指针
  21.     create_raw_pointer_use_box();
  22.     //4.0 如何创建一个空的原始指针
  23.     create_empty_raw_pointer();
  24.     //5.0 演示如果使用原始指针把一个数组切成两半,且每一半都是可变的切片
  25.     demo_split_slice_to_two_half();
  26. }
  27. /**
  28. * 创建一个指向任意内存地址的原始指针
  29. */
  30. #[allow(dead_code)]
  31. fn create_raw_pointer_from_address() {
  32.     let address = 0x012345usize;
  33.     let r = address as *const i32;
  34.     unsafe {
  35.         println!("r: {}", *r);
  36.     }
  37. }
  38. /**
  39. * 使用Box创建原始指针
  40. */
  41. fn create_raw_pointer_use_box() {
  42.     //回忆下Box指针,我们知道Box指针是一个堆上分配的智能指针。
  43.     let boxed = Box::new(5);
  44.     let five = *boxed;
  45.     println!("five: {}", five);
  46.     let brave = Box::new(String::from("Rust"));
  47.     let raw_brave = Box::into_raw(brave);
  48.     unsafe{
  49.         println!("raw_brave: {:?}", *raw_brave);
  50.     }
  51.     //现在需要手动释放内存,否则会造成内存泄露
  52.     println!("释放raw_brave");
  53.     unsafe{
  54.         drop(Box::from_raw(raw_brave));
  55.     }
  56.     println!("释放raw_brave完成");
  57.     // unsafe{
  58.     //     println!("raw_brave依然存在,但这个时候它应该是一个空的: {:?}", *raw_brave);
  59.     // }
  60. }
  61. /**
  62. * 创建一个空的原始指针-利用std::ptr::null
  63. */
  64. fn create_empty_raw_pointer() {
  65.     let mut ptr = std::ptr::null::<i32>();
  66.     if ptr.is_null() {
  67.         println!("指针ptr是空的");
  68.     } else {
  69.         println!("指针ptr不是空的");
  70.     }
  71.     let mut value = 5;
  72.     unsafe {
  73.         // 将指针指向一个有效的内存地址
  74.         ptr = &value as *const i32;
  75.         // 读取指针的值
  76.         if !ptr.is_null() {
  77.             println!("指针ptr现在的值是: {}", *ptr);
  78.         } else {
  79.             println!("指针ptr现在是空的");
  80.         }
  81.     }
  82. }
  83. /**
  84. * 演示如果使用原始指针把一个数组切成两半,且每一半都是可变的切片
  85. */
  86. fn demo_split_slice_to_two_half() {
  87.     let mut scores=[10,20,30,40,50];
  88.     println!("原始scores: {:?}", scores);
  89.     let (left, right) = unsafe_slice(&mut scores, 3);
  90.     println!("scores-left: {:?}, scores-right: {:?}", left, right);
  91.     //现在改改左边第一个,看是不是发生了效果
  92.     left[0] = 1024;
  93.     right[0] = 1975;
  94.     println!("修改后scores: {:?}", scores);  //事实证明了这点:左边改了,所以可变切片可以的
  95.     let mut poems=["独怜幽草涧边生","上有黄丽深树鸣","春潮带雨晚来急"];
  96.     let (left, right) = unsafe_slice(&mut poems, 2);
  97.     println!("left: {:?}, right: {:?}", left, right);
  98. }
  99. /**
  100. * 使用原始指针创建切片
  101. * 这里有非常关键的说明:(来自书本)
  102. * Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同部分:它只知道我们借用了同一个 slice 两次。
  103. * 本质上借用 slice 的不同部分是可以的,因为结果两个 slice 不会重叠,不过 Rust 还没有智能到能够理解这些
  104. *
  105. * 这一段至少说明了:rust的编译器也不是万能的,要是万能就太慢了。
  106. * 在rust书中,此例子属于:创建不安全代码的安全抽象  。
  107. * 即把不安全的操作封装起来,对外提供安全的接口(函数)
  108. * @param values 实际是一个数组的完整切片
  109. * @param mid 切片中间的索引位置
  110. */
  111. fn unsafe_slice<T>(values: &mut [T], mid: usize)-> (&mut [T], &mut [T]) {
  112.     //let mut data = [1, 2, 3];
  113.     //let ptr = &data as *const _;
  114.     let len = values.len();
  115.     let ptr = values.as_mut_ptr();   // 切片转为原始指针,且指向切片的起始位置。
  116.     //方法from_raw_parts_mut用于从原始指针和长度创建一个可变切片。
  117.     //注意:这里的add方法,是原始指针的一个方法,用于计算偏移后的新地址。
  118.     //如果切片中保存的是汉字,那么应该加多少了?   
  119.     unsafe {
  120.         (
  121.             slice::from_raw_parts_mut(ptr, mid),
  122.             slice::from_raw_parts_mut(ptr.add(mid), len - mid),
  123.         )
  124.     }
  125. }
复制代码
 
输出如下:

 
有几点值得说:

  • 不安全代码必须写在unsafe{}代码块中,否则会报错。
  • rust的编译器偶然候会有问题
  • 原始指针原则上必要自己手动开释,但也不是所有的都必要。如果是借用来的,可以不要;如果是Box创建的,可以开释
  • 随意访问内存一块区域,可能会导致步伐停止
五、小结


  • 作为一个告急的补充,原始指针为rust编程提供了机动性,能够做安全指针所不能完成的一些工作,主要是内存操作和外部函数接口、高级数据结构等
  • rust有多种方式可以创建原始指针,包括使用引用、Box指针创建、空指针创建
  • 部门原始指针必要手动开释,部门不必要,主要看如何定义,这个必要特别留意
  • 原始指针的存在,再一次证明了个性化的、简单的东西能够更加高效,能力也更强,只是对于使用者的水平要求较高

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

欢乐狗

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表