湘潭企业网站建设/百度在线客服中心
这篇文章简单介绍下unsafe rust的几个要点
1. 解引用裸指针
裸指针其实就是C++或者说C的指针,与C的指针不同的是,Rust的裸指针还是要分为可变和不可变,*const T
和 *mut T
:
基于引用创建裸指针
let mut num = 5;let r1 = &num as *const i32;let r2 = &mut num as *mut i32;
或者不想用类型转换也可以这么写(书上认为这是一种隐式转换,我觉得就是一种类型声明)
let r3: *const i32 = #let r4: *mut i32 = &mut num;
创建裸指针是安全的行为,而解引用裸指针才是不安全的行为
fn main() {let mut num = 5;let r1 = &num as *const i32;unsafe {println!("r1 is: {}", *r1);}
}
基于内存地址创建裸指针
基于内存地址创建裸指针相当于直接给指针赋值为某个内存地址:
use std::{slice::from_raw_parts, str::from_utf8_unchecked};fn main() {let string = "bluebonnet27";//as_ptr: Converts a string slice to a raw pointer.let pointer_num = string.as_ptr() as usize;let length = string.len();unsafe {//from_raw_parts: Forms a slice from a pointer and a length.//from_utf8_unchecked: Converts a slice of bytes to a string slice without checking that the string contains valid UTF-8let res = from_utf8_unchecked(from_raw_parts(pointer_num as *const u8, length));println!("The {} bytes at 0x{:X} stored: {}",length, pointer_num, res)}
}
我们可以尝试将pointer_num
和length
改成其他值
基于智能指针创建裸指针
还有一种创建裸指针的方式,那就是基于智能指针来创建:
let a: Box<i32> = Box::new(10);
// 需要先解引用a
let b: *const i32 = &*a;
// 使用 into_raw 来创建
let c: *const i32 = Box::into_raw(a);
在C++中也可以通过智能指针创建裸指针,并且这种做法也存在一些问题。比如如下的代码:
auto p = make_shared<int>(42);
int* iPtr = p.get();
{shared_ptr<int>(iPtr);
}int value = *p; // Error! 内存已经被释放
p与iPtr指向了相同的内存,然而通过get方法后,将内存管理权转移给了普通指针。iPtr传递给里面程序块的临时智能指针后,引用计数为1,随后出了作用域,减少为0,释放内存。
2. 调用 unsafe 函数或方法
很简单,加上unsafe的声明就行:
unsafe fn dangerous() {}
fn main() {dangerous();
}
这样是编译不过的,因为dangerous
是个unsafe
函数。加上unsafe
调用即可:
unsafe fn dangerous() {}
fn main() {unsafe {dangerous();}
}
借用官方文档的一句话,“在整个代码库(code base,指构建一个软件系统所使用的全部代码)中,要尽可能减少不安全代码的量”,比如我们上面的这个例子:
fn main() {let string = "bluebonnet27";//as_ptr: Converts a string slice to a raw pointer.let pointer_num = string.as_ptr() as usize;let length = string.len();unsafe {//from_raw_parts: Forms a slice from a pointer and a length.//from_utf8_unchecked: Converts a slice of bytes to a string slice without checking that the string contains valid UTF-8let res = from_utf8_unchecked(from_raw_parts(pointer_num as *const u8, length));println!("The {} bytes at 0x{:X} stored: {}",length, pointer_num, res)}
}
printlin!
是个安全函数,将它放在unsafe唯一的原因是,我们需要在res
的生命周期内打印它。所以我们可以改成这样:
fn get_str(pointer_num: usize, length: usize) -> String {unsafe {//from_raw_parts: Forms a slice from a pointer and a length.//from_utf8_unchecked: Converts a slice of bytes to a string slice without checking that the string contains valid UTF-8String::from(from_utf8_unchecked(from_raw_parts(pointer_num as *const u8,length,)))}
}fn main() {let string = "bluebonnet27";//as_ptr: Converts a string slice to a raw pointer.let pointer_num = string.as_ptr() as usize;let length = string.len();let res = get_str(pointer_num, length);println!("The {} bytes at 0x{:X} stored: {}",length, pointer_num, res)
}
我们将unsafe的部分单独抽成了一个函数。这里的返回值,不想用String
交出所有权,也可以用'static
的&str
或者更简单地,可以直接将res右侧全部用unsafe包裹:
let res = unsafe{ from_utf8_unchecked(from_raw_parts(pointer_num as *const u8, length));}
3. FFI
FFI(Foreign Function Interface)可以用来与其它语言进行交互,将 C/C++ 的代码重构为 Rust 时,先将相关代码引入到 Rust 项目中,然后逐步重构,也是不错的。
当然,除了 FFI 还有一个办法可以解决跨语言调用的问题,那就是将其作为一个独立的服务,然后使用网络调用的方式去访问,HTTP,gRPC 都可以。
言归正传,之前我们提到 unsafe 的另一个重要目的就是对 FFI 提供支持,它的全称是 Foreign Function Interface,顾名思义,通过 FFI , 我们的 Rust 代码可以跟其它语言的外部代码进行交互。
在Rust中调用其他语言的函数
下面的例子演示了如何调用 C 标准库中的 abs
函数(Rust 目前无法直接调用 C++ 库):
extern "C" {fn abs(input: i32) -> i32;
}fn main() {unsafe {println!("Absolute value of -3 according to C: {}", abs(-3));}
}
事实上,不指定 ABI 字符串的默认情况下,外部块会假定使用指定平台上的标准 C ABI 约定来调用当前的库。所以上面的代码这么写也是ok的:
extern {fn abs(input: i32) -> i32;
}
当然大括号不能去掉。在 extern “C” 代码块中,我们列出了想要调用的外部函数的签名。其中 “C” 定义了外部函数所使用的应用二进制接口ABI (Application Binary Interface):ABI 定义了如何在汇编层面来调用该函数。
有三个 ABI 字符串是跨平台的,并且保证所有编译器都支持它们:
extern "Rust"
– 在任何 Rust 语言中编写的普通函数 fn foo() 默认使用的 ABI。extern "C"
– 这等价于 extern fn foo();无论您的 C编译器支持什么默认 ABI。extern "system"
– 在 Win32 平台之外,中通常等价于extern "C"
。在 Win32 平台上,应该使用"stdcall"
,或者其他应该使用的 ABI 字符串来链接它们自身的 Windows API。
4. 访问或修改一个可变的静态变量
静态变量
静态变量允许声明一个全局的变量,常用于全局数据统计,例如我们希望用一个变量来统计程序当前的总请求数
static mut REQUEST_RECV: usize = 0;
fn main() {unsafe {REQUEST_RECV += 1;assert_eq!(REQUEST_RECV, 1);}
}
Rust 要求必须使用unsafe语句块才能访问和修改static变量,因为这种使用方式往往并不安全,其实编译器是对的,当在多线程中同时去修改时,会不可避免的遇到脏数据。
只有在同一线程内或者不在乎数据的准确性时,才应该使用全局静态变量。
和常量相同,定义静态变量的时候必须赋值为在编译期就可以计算出的值(常量表达式/数学表达式),不能是运行时才能计算出的值(如函数)
5. 实现 unsafe 特征
unsafe特征的意义是,特征中存在unsafe的方法,有时候就得需要unsafe的特征:
unsafe trait Foo {// 方法列表
}unsafe impl Foo for i32 {// 实现相应的方法
}fn main() {}
但是在调用 unsafe trait 时,直接直接调用,不需要在 unsafe 块中调用,因为这里的安全已经被实现者保证了,毕竟如果实现者没保证,调用者也做不了什么来保证安全.
Rust 中的 Send
/ Sync
,这两个 trait 都是 unsafe trait,定义如下
pub unsafe auto trait Send {}
pub unsafe auto trait Sync {}
6. 访问 union 中的字段
访问
这个从C中继承而来的数据结构,在Rust中也大多用于和C进行交互,下面就是一个union
的例子:
union MyUnion {f1: u32,f2: f32,
}
union的关键属性是其所有字段共享公共存储。 因此,对union的一个字段的写入可以覆盖其他字段,并且 union的大小由其最大字段的大小决定。
fn main() {//初始化一个union,语法和struct类似let u = MyUnion { f1: 1 };//读取union的值let f = unsafe { u.f1 };println!("u.f1 = {f}");
}
读取值的操作是unsafe的,这也很好理解,编译器并不知道你读取的东西有没有初始化。反正大家都用相同的内存,我说这段数据就是f32
也行,就算它存进去的时候其实是u32
。
let f = unsafe { u.f1 };let tmp = unsafe { u.f2 };println!("u.f1 = {f}");println!("u.f2 = {tmp}");
结果如下:
也可以用模式匹配,当然,这种操作和直接读取没什么区别,所以也必须是unsafe的:
unsafe {match u {MyUnion { f1: 1 } => {println!("one");}MyUnion { f2 } => {println!("{}", f2);}}}
引用
引用操作也是unsafe
的,而且,由于union
各个成员是共享内存的,对一名成员的引用会视为对其他所有成员的引用:
// 错误: 不能同时对 `u` (通过 `u.f2`)拥有多于一次的可变借用
fn test() {let mut u = MyUnion { f1: 1 };unsafe {let b1 = &mut u.f1;
// ---- 首次可变借用发生在这里 (通过 `u.f1`)let b2 = &mut u.f2;
// ^^^^ 二次可变借用发生在这里 (通过 `u.f2`)*b1 = 5;}
// - 首次借用在这里结束assert_eq!(unsafe { u.f1 }, 5);
}
Rust-Analysis也给出了提示:
C++的改进
union存在很多问题,因此C++17设计了一个新的variant
替代原来的union
。
variant的用法如下:
using namespace std;int main()
{variant<int, string, float> myVar;myVar = "Hello variant";
}
union
访问的时候,由于每个成员变量都有自己的变量名,因此直接就可以访问。但是variant
不太行,而且还要更麻烦一点。
最简单的就是用get
cout << get<string>(myVar) << endl;
但是这里存在一个问题,如果类型对了那皆大欢喜;类型错了,还要处理抛出的std::bad_variant_access
异常:
我们可以使用get_if
,先判断类型再进行访问。get_if
判断类型成功会返回指向数据的指针,判断失败会返回空指针。
if(auto ptr = get_if<string>(&myVar))
{cout << *ptr << endl;
}
7. 内联汇编
Rust中的内联汇编
Rust 提供了 asm!
宏,可以让大家在 Rust 代码中嵌入汇编代码,对于一些极致高性能或者底层的场景还是非常有用的,例如操作系统内核开发。
use std::arch::asm;unsafe {asm!("nop");
}
上面代码将插入一个 NOP 指令( 空操作 ) 到编译器生成的汇编代码中,其中指令作为 asm!
的第一个参数传入。
总结
C++中其实没有unsafe这个东西,像类似裸指针这种,在C++中甚至是一种比较常用的用法。毕竟智能指针,比如shared_ptr
,unique_ptr
,用法更为复杂。
所以我个人认为,Rust的unsafe的意义是,将这些不安全的操作变得复杂,变得难写,进而引导程序员选择更加简单,更加好写的安全用法。这和C++如今的处境刚好相反,C++中按照安全原则写出来的代码都比较复杂,这也是历史原因,毕竟不能动现成的代码。
另外,unsafe也是一种承诺,不再由编译器保证代码的安全性,而是由程序员自己来保证。一旦代码出问题,责任全在程序员自己。
相关文章:

【Rust】unsafe rust入门
这篇文章简单介绍下unsafe rust的几个要点 1. 解引用裸指针 裸指针其实就是C或者说C的指针,与C的指针不同的是,Rust的裸指针还是要分为可变和不可变,*const T 和 *mut T: 基于引用创建裸指针 let mut num 5;let r1 &num …...

dpwwn02靶场
靶机下载地址:https://download.vulnhub.com/dpwwn/dpwwn-02.zip 信息收集 ip add 查看kali Linux虚拟机的IP为:10.10.10.128 https://vulnhub.com/entry/dpwwn-2,343/中查看靶机的信息,IP固定为10.10.10.10 所以kali Linux添加仅主机网卡…...

K8S疑难概念理解——Pod,应该以哪种Kind来部署应用,为什么不直接Pod这种kind?
文章目录 一、Pod概念深度理解,为什么一般不直接以kindPod资源类型来部署应用?二、究竟应该以哪种资源类型来部署应用 一、Pod概念深度理解,为什么一般不直接以kindPod资源类型来部署应用? Pod是Kubernetes中的最小部署单元,可以包含一个或…...

LabVIEW进行仪器串行通信与模拟信号采集的比较
在现代测试、测量和控制系统中,设备通常采用两种主要方式与计算机进行交互:一种是通过数字通信接口(如RS-232、RS-485、GPIB等),另一种是通过模拟信号(电压、电流)进行数据输出。每种方式具有其…...

D81【 python 接口自动化学习】- python基础之HTTP
day81 requests请求session用法 学习日期:20241127 学习目标:http定义及实战 -- requests请求session用法 学习笔记: requests请求session用法 import requests# 创建一个会话 reqrequests.session() url "http://sellshop.5istud…...

白鹿 Hands-on:消除冷启动——基于 Amazon Lambda SnapStart 轻松打造 Serverless Web 应用(二)
文章目录 前言一、前文回顾二、在 Lambda 上运行2.1、查看 Amazon SAM template2.2、编译和部署到 Amazon Lambda2.3、功能测试与验证 三、对比 Snapstart 效果四、资源清理五、实验总结总结 前言 在这个环节中,我们将延续《白鹿 Hands-on:消除冷启动——…...

ROC曲线
文章目录 前言一、ROC的应用?二、使用方式1. 数据准备2.绘图可视化 前言 在差异分析中,ROC曲线可以用来评估不同组之间的分类性能差异。差异分析旨在比较不同组之间的特征差异,例如在基因表达研究中比较不同基因在不同条件或组织中的表达水平…...

c++ 位图和布隆过滤器
位图(bitmap) 定义 位图是一种使用位数组存储数据的结构。每一位表示一个状态,通常用于快速判断某个值是否存在,或者用来表示布尔类型的集合。 特点 节省空间:一个字节可以表示8个状态。高效操作:位操作…...

阿里云CPU过载的一点思考
现象:阿里云ECS服务器连续5个周期CPU超90%告警 分析: max_connections和max_user_connections都做了限制,但是依然告警,服务器上有四个子服务,查看了每个服务的配置文件,发现使用同一个数据库账号&#x…...

单片机学习笔记 15. 串口通信(理论)
更多单片机学习笔记:单片机学习笔记 1. 点亮一个LED灯单片机学习笔记 2. LED灯闪烁单片机学习笔记 3. LED灯流水灯单片机学习笔记 4. 蜂鸣器滴~滴~滴~单片机学习笔记 5. 数码管静态显示单片机学习笔记 6. 数码管动态显示单片机学习笔记 7. 独立键盘单片机学习笔记 8…...

算法训练营day22(二叉树08:二叉搜索树的最近公共祖先,插入,删除)
第六章 二叉树part08 今日内容: ● 235. 二叉搜索树的最近公共祖先 ● 701.二叉搜索树中的插入操作 ● 450.删除二叉搜索树中的节点 详细布置 235. 二叉搜索树的最近公共祖先 相对于 二叉树的最近公共祖先 本题就简单一些了,因为 可以利用二叉搜索树的…...

Linux history 命令详解
简介 history 命令显示当前 shell 会话中以前执行过的命令列表。这对于无需重新输入命令即可重新调用或重新执行命令特别有用。 示例用法 显示命令历史列表 history# 示例输出如下:1 ls -l 2 cd /var/log 3 cat syslog执行历史记录中的命令 !<number>…...

Kafka知识体系
一、认识Kafka 1. kafka适用场景 消息系统:kafka不仅具备传统的系统解耦、流量削峰、缓冲、异步通信、可扩展性、可恢复性等功能,还有其他消息系统难以实现的消息顺序消费及消息回溯功能。 存储系统:kafka把消息持久化到磁盘上,…...

【Android】EventBus的使用及源码分析
文章目录 介绍优点基本用法线程模式POSTINGMAINMAIN_ORDEREDBACKGROUNDASYNC 黏性事件 源码注册getDefault()registerfindSubscriberMethods小结 postpostStickyunregister 介绍 优点 简化组件之间的通信 解耦事件发送者和接收者在 Activity、Fragment 和后台线程中表现良好避…...

【大数据学习 | Spark调优篇】Spark之内存调优
1. 内存的花费 1)每个Java对象,都有一个对象头,会占用16个字节,主要是包括了一些对象的元信息,比如指向它的类的指针。如果一个对象本身很小,比如就包括了一个int类型的field,那么它的对象头实…...

Linux:文件系统inode
早期,存储文件的设备是磁盘(当下的市场几乎都是SSD),但大家习惯的把它们都称为磁盘,磁盘是用来表示区分内存的存储设备。而在操作系统看来,这个存储设备的结构就是一个线性结构,这一点很重要。 …...

力扣难题解析
滑动窗口问题 76.最小覆盖子串 题目链接:76. 最小覆盖子串 - 力扣(LeetCode) 题目描述: 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空…...

4.5-Channel 和 Flow:SharedFlow 和 StateFlow
文章目录 SharedFlow数据流的收集和事件订阅的区别launchIn() 和 shareIn() 的区别SharedFlow 与 Flow、Channel 的区别shareIn() 适用场景 shareIn() 的具体参数说明shareIn() 的 replay 参数shareIn() 的 started 参数WhileSubscribed() 的参数及适用场景 MutableSharedFlow、…...

Qt | TCP服务器实现QTcpServer,使用线程管理客户端套接字
点击上方"蓝字"关注我们 01、QTcpServer >>> QTcpServer 是 Qt 网络模块中的一个类,用于实现TCP服务器。它允许创建一个服务器,可以接受来自客户端的连接。QTcpServer 是事件驱动的,这意味着它将通过信号和槽机制处理网络事件。 常用函数 构造函数: QT…...

【提高篇】3.6 GPIO(六,寄存器介绍,下)
目录 2.3 输出速度寄存器OSPEEDR(GPIOx_OSPEEDR) (x = A..I) 2.4 上拉/下拉寄存器 (GPIOx_PUPDR) (x = A..I) 2.5 输入数据寄存器(IDR) 2.6 输出数据寄存器(ODR) 2.7 置位/复位寄存器(BSRR) 2.8 BSRR与ODR寄存器的区别 2.3 输出速度寄存器OSPEEDR(GPIOx_OSPEEDR) (…...

【AI】数据,算力,算法和应用(3)
三、算法 算法这个词,我们都不陌生。 从接触计算机,就知道有“算法”这样一个神秘的名词存在。象征着专业、权威、神秘、高难等等。 算法是一组有序的解决问题的规则和指令,用于解决特定问题的一系列步骤。算法可以被看作是解决问题的方法…...

深度学习笔记——生成对抗网络GAN
本文详细介绍早期生成式AI的代表性模型:生成对抗网络GAN。 文章目录 一、基本结构生成器判别器 二、损失函数判别器生成器交替优化目标函数 三、GAN 的训练过程训练流程概述训练流程步骤1. 初始化参数和超参数2. 定义损失函数3. 训练过程的迭代判别器训练步骤生成器…...

网络安全开源组件
本文只是针对开源项目进行收集,如果后期在工作中碰到其他开源项目将进行更新。欢迎大家在评论区留言,您在工作碰到的开源项目。 祝您工作顺利,鹏程万里! 一、FW(防火墙) 1.1 pfSense pfSense项目是一个免费…...

Python毕业设计选题:基于django+vue的智慧社区可视化平台的设计与实现+spider
开发语言:Python框架:djangoPython版本:python3.7.7数据库:mysql 5.7数据库工具:Navicat11开发软件:PyCharm 系统展示 管理员登录 管理员功能界面 养老机构管理 业主管理 社区安防管理 社区设施管理 车位…...

Oracle LinuxR7安装Oracle 12.2 RAC集群实施(DNS解析)
oracleLinuxR7-U6系统Oracle 12.2 RAC集群实施(DNS服务器) 环境 RAC1RAC2DNS服务器操作系统Oracle LinuxR7Oracle LinuxR7windows server 2008R2IP地址172.30.21.101172.30.21.102172.30.21.112主机名称hefei1hefei2hefei数据库名hefeidbhefeidb实例名…...

M2芯片安装es的步骤
背景:因为最近经常用到es,但是测试环境没有es,自己本地也没安装,为了方便测试,然后安装一下,但是刚开始安装就报错,记录一下,安装的版本为8.16.1 第一步:去官网下载maco…...

macos下brew安装redis
首先确保已安装brew,接下来搜索资源,在终端输入如下命令: brew search redis 演示如下: 如上看到有redis资源,下面进行安装,执行下面的命令: brew install redis 演示效果如下: …...

第六届金盾信安杯-SSRF
操作内容: 进入环境 可以查询网站信息 查询环境url https://114.55.67.167:52263/flag.php 返回 flag 就在这 https://114.55.67.167:52263/flag.php 把这个转换成短连接,然后再提交 得出 flag...

【论文投稿】国产游戏技术:迈向全球引领者的征途
【IEEE出版南方科技大学】第十一届电气工程与自动化国际会议(IFEEA 2024)_艾思科蓝_学术一站式服务平台 更多学术会议论文投稿请看:https://ais.cn/u/nuyAF3 目录 国产游戏技术能否引领全球? 一、国产游戏技术的崛起之路 1.1 初期探索与积…...

腾讯微众银行大数据面试题(包含数据分析/挖掘方向)面试题及参考答案
为什么喜欢使用 XGBoost,XGBoost 的主要优势有哪些? XGBoost 是一个优化的分布式梯度增强库,在数据科学和机器学习领域应用广泛,深受喜爱,原因主要在于其众多突出优势。 首先,它的精度高,在许多机器学习竞赛和实际应用中,XGBoost 都展现出卓越的预测准确性。其基于决策…...