当前位置: 首页 > news >正文

玩转Rust高级应用 如何避免对空指针做“解引用”操作,在C/C++ 里面就是未定义行为

如果你想表达这个类型对T 类型成员有拥有关系,那么可以使用PhantomData。例如std::core::ptr::Unique:

pub     Unique<T:?Sized>{pointer:NonZero<*const T>,_marker:PhantomData<T>,
}

如果你想表达这个类型对T 类型成员有借用关系,那么可以使用PhantomData<&'a T>。你还可以用它来表明当前这个类型不可Send 、Sync,示例如下:

struct MyStruct{data:String,_marker:PhantomData<*mut()>
}

下面同样用比较完整的示例来演示一下这个类型的具体作用。假设我们现在有两个类型:

use std::fmt::Debug;
#[derive(Clone,Debug)]
struct S;
#[derive(Debug)]
struct R<T:Debug>{x:*const T
}

其中R 类型想表达一种借用关系,它内部需要用裸指针实现。上面这种简单的写法是有 问题的,因为我们可以很容易制造出悬空指针:

fn main(){let mut r=R{x:std::ptr::null()};{let local =S{};r.x =&local;}//r.x now is dangling pointer
}

为了让编译器使用borrow checker 检查这种内存错误,我们可以给R 类型添加一个生命 周期参数,并且利用PhantomData 使用这个生命周期参数,避免“未使用泛型参数”的错误。 同时给R 类型增加一个成员方法,在成员方法中改变指针的地址,并且通过模块系统禁止外 部用户直接访问R 的内部成员。完整代码如下所示:

use std::fmt::Debug;
use std::ptr::null;
use std::marker::PhantomData;
#[derive(Clone,Debug)]
struct S;
#[derive(Debug)]
struct R<'a,T:Debug +'a>{X:*const T,marker:PhantomData<&'a T>,
}
impl<'a,T:Debug>Drop for R<'a,T>{fn drop(&mut self){unsafe{println!("Dropping R while S{:?}",*self.x)}}
}
impl<'a,T:Debug +'a>R<'a,T>{pub fn ref_to<'b:'a>(&mut self,obj:&'bT){self.x =obj;}
}
fn main(){let mut r=R{x:null(),marker:PhantomData };{let local =S{};r.ref_to(&local);}
}

再编译,我们可以看到,这次编译器就可以成功发现生命周期错误,禁止悬挂指针的产 生。在写FFI 给 C 代码做封装的时候,需要经常使用裸指针,这时就可以用类似的技巧来处 理生命周期的问题。

未定义行为

在 C/C++ 等语言中,未定义行为 (undefined behavior, 简称UB) 指的是,在某些情况下语 言标准允许编译器做任何事情,无论发生什么后果都是正常的,不属于编译器的bug 。比如, 对空指针做“解引用”操作,在C/C++ 里面就是未定义行为,编译器可以决定做任何事情。

在 C/C++ 标准里面,很多情况下,都有充分的理由将某些行为定义为UB, 这是语言本 身的定位决定的。它可以简化编译器的设计,也可以最大化执行效率,还可以最大化跨平 台,等等。但是我们也应该承认,过多的UB 是对用户极其不友好的。在Rust 里 面 ,UB 被 限制在了一个较小的范围内,只有unsafe代码有可能制造出UB, 这也是在写unsafe代码的 时候需要注意的。
下面列举一些undefined behavior, 摘抄自Rust 的 Reference 文档:

  • 数据竞争
  • 解引用空指针或者悬空指针
  • 使用未对齐的指针读写内存而不是使用read_unaligned或者write_unaligned 口读取未初始化内存
  • 破坏了指针别名规则
  • 通过共享引用修改变量(除非数据是被UnsafeCell包裹的)
  • 调用编译器内置函数制造UB
  • 给内置类型赋予非法值
    ●给引用或者Box 赋值为空或者悬空指针
    ● 给bool类型赋值为0和1之外的数字
    ● 给enum 类型赋予类型定义之外的 tag标记
    ● 给char类型赋予超过char::MAX 的值
    ● 给str类型赋予非utf-8编码的值
    以上只是一些典型的问题,完整列表请大家参考官方文档。这些问题只可能在写unsafe 代码的时候出现,这都是需要读者注意的地方。

Rust 的 unsafe 关键字是一个难点,也是很多初学者困惑的地方。很多人有这样的疑惑: 既然Rust 允许使用unsafe 来完成许多危险的操作,那么Rust 的安全性保证是不是就没什么 意义了?

这件事情不能这么理解。unsafe 的存在不是来故意破坏安全性的,它只是一种面向更底 层操作的接口。不同的高级语言对于什么是底层的定义是不同的,但是所有的高级语言,只 要不断往底层探究,总会碰到safe与 unsafe之间的分界线。比如,Java有自己的JNI机制, C# 也有unsafe 关键字,Python 也可以调用C 模块,甚至C/C++ 语言都可以内嵌汇编。当你 在高级语言中与底层操作交互的时候,必须确保高级语言中的一些规则和约定。

Java 、C# 这 类语言,利用GC 实现了内存安全,但是用户同样可以使用JNIunsafe 实现不安全的操作,但 这件事情并不意味着Java 、C# 语言本身有安全性缺陷。同理,在C 语言里面用内嵌汇编搞乱 了堆栈,也不能说是C 语言的设计缺陷。只不过是用户使用这些机制的时候,没有一个自动 检查工具来保证安全性,而是必须由自己来保证上层代码和下层代码之间交互的正确性。

Rust 的 unsafe 最大的问题在于,到目前为止,依然没有一份官方文档来明确哪些东西是 用户可以依赖的、哪些是编译器实现相关的、哪些是以后永远不变的、哪些是将来可能会有 变化的。所以,哪怕用户能确保自己写出来的unsafe 代码在目前版本上是完全正确的,也没 办法确保不会在以后的版本中出问题。

如果以后编译器的实现发生了变化,导致了unsafe 代 码无法正常工作,究竟算是编译器的bug 还是用户错误地依赖了某些特性,还说不清楚。正 式的unsafe guideline 还在继续编写过程中。(当然这种错误情况几率是很低的,绝大多数用 户使用unsafe 的时候都是在FFI 场景下,不会涉及那些精微细密的语义规则。)

我们既不能过于滥用unsafe,也不该对它心怀恐惧。它只是表明,某些代码的安全性依 赖于某些条件,而我们无法清晰地在代码中表达这些约束条件,因此无法由编译器帮我们自 动检查。

unsafe 是 Rust 的一块重要拼图,充分理解unsafe 的意义和作用,才能让我们更好地理解 safe 的来源和可贵。

本节将通过一个比较完整的例子,把内存安全问题分析一遍。本节选择的例子是标准库 中的基本数据结构Vec 源代码分析。之所以选择这个模块作为示例,其一是因为这个类 型作为非常基础的数据结构,平时用得很多,大家都很熟悉;第二个原因是,恰好它的内部 实现又完全展现了Rust 内存安全的方方面面,深入剖析它的内部实现非常有利于加深我们对 Rust 内存安全的认识。本章中用于分析的代码是1.23 nightly 版本,Vec 的内部实现源码在 此之前一直有所变化,以后也很可能还会有变化,请读者注意这一点。

我们先从使用者的角度分析 一 下Vec 是如何自动管理内存空间的:

fn main(){let mut v1 =Vec::<i32>::new();println!("Start:length{}capacity{}",v1.len(),v1.capacity());for i in 1..10{v1.push(i);println!("[Pushed{}]length{}capacity{}",i,v1.len(), v1.capacity());}let mut v2 =Vec::<i32>::with_capacity(1);println!("Start:length{}capacity{}",v2.len(),v2.capacity());v2.reserve(10);for i in 1..10{v2.push(i);println!("[Pushed{}]length{}capacity{}",i,v2.len(), v2.capacity());}
}

编译,执行,从结果中可以看出,如果用new 方法构造出来,一开始的时候是没有分配内存空间的,capacity 为0。我们也可以使用with_capacity来构造新的Vec, 可以自行 指定预留空间大小,还可以对已有的Vec 调用reserve 方法扩展预留空间。在向容器内部 插入数据的时候,如果当前容量不够用了,它会自动申请更多的空间。当变量生命周期结束 的时候,它会自动释放它管理的内存空间。

http://www.dtcms.com/a/582342.html

相关文章:

  • 音视频处理(三):hls协议和m3u8详解和视频下载爬虫实战
  • Java面试全生态图谱(2025体系版)
  • 亳州做网站的公司公司品牌网站建设
  • 泉州网站公司wordpress 搜索乱码
  • SG-EIP-MOD-210(EtherNet/IP 转 Modbus 网关)
  • TDengine 字符串函数 CONCAT_WS 用户手册
  • SmartDV宣布其MIPI® SoundWire® I3S℠ 1.0 IP产品组合已向多家客户提供授权
  • 如何将本地文件上传至Github?【详细解释】
  • 酒店网站可以怎么做网站下雪代码
  • 【自然语言处理】预训练05:全局向量的词嵌入(GloVe)
  • 中国男女直接做的视频网站学做土建资料员的网站
  • Data engineering at Meta
  • 开创视觉方案新范式!INDEMIND四目空间智能方案让导航从追求“精准”迈向“智能协同”
  • 智慧引擎,零碳未来:安科瑞EMS3.0赋能省园区高效低碳转型
  • 在 UNS 中如何使用 Avro + Schema Registry 管理 MQTT 数据模型
  • AI辅助数据分析和学习了没?
  • 海东电子商务网站建设代做seo排名
  • 网站建设公司联系电话如何更新目录wordpress
  • 网络安全:WebShell
  • Qt历险记精心整理通用C++ 缓冲区和智能指针分享
  • SAP PP入库单浏览报表分享
  • 网站代运营合同西宁网站设计制作
  • 车牌识别接口技术深度解析:智慧交通,多场景开发者OCR API解决方案
  • 电子商务网站建设实训心得做网站有了空间在备案吗
  • (128页PPT)麦肯锡三十年经典培训教材金字塔原理思考写作和解决问题的逻辑培训演示教材(附下载方式)
  • 义乌外贸网站制作买东西的网站都有哪些
  • 华为OD算法开发指导-简易内存池
  • 外贸网站建设石家庄个人网站需不需要搭建服务器
  • 网站建设 信息化程度大象影视传媒制作公司
  • 数据结构---并查集实现