【Rust】unsafe rust入门

这篇文章简单介绍下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_numlength改成其他值
失败的结果

基于智能指针创建裸指针

还有一种创建裸指针的方式,那就是基于智能指针来创建:

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也是一种承诺,不再由编译器保证代码的安全性,而是由程序员自己来保证。一旦代码出问题,责任全在程序员自己。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/482068.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么是人工智能大模型?

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于人工智能大模型的相关内容&#xff01; …

基于深度学习和卷积神经网络的乳腺癌影像自动化诊断系统(PyQt5界面+数据集+训练代码)

乳腺癌是全球女性中最常见的恶性肿瘤之一&#xff0c;早期准确诊断对于提高生存率具有至关重要的意义。传统的乳腺癌诊断方法依赖于放射科医生的经验&#xff0c;然而&#xff0c;由于影像分析的复杂性和人类判断的局限性&#xff0c;准确率和一致性仍存在挑战。近年来&#xf…

【IMF靶场渗透】

文章目录 一、基础信息 二、信息收集 三、flag1 四、flag2 五、flag3 六、flag4 七、flag5 八、flag6 一、基础信息 Kali IP&#xff1a;192.168.20.146 靶机IP&#xff1a;192.168.20.147 二、信息收集 Nmap -sP 192.168.20.0/24 Arp-scan -l nmap -sS -sV -p- -…

MySQL 复合查询

实际开发中往往数据来自不同的表&#xff0c;所以需要多表查询。本节我们用一个简单的公司管理系统&#xff0c;有三张表EMP,DEPT,SALGRADE 来演示如何进行多表查询。表结构的代码以及插入的数据如下&#xff1a; DROP database IF EXISTS scott; CREATE database IF NOT EXIST…

理解Java集合的基本用法—Collection:List、Set 和 Queue,Map

本博文部分参考 博客 &#xff0c;强烈推荐这篇博客&#xff0c;写得超级全面&#xff01;&#xff01;&#xff01; 图片来源 Java 集合框架 主要包括两种类型的容器&#xff0c;一种是集合&#xff08;Collection&#xff09;&#xff0c;存储一个元素集合&#xff08;单列…

【看海的算法日记✨优选篇✨】第三回:二分之妙,寻径中道

&#x1f3ac; 个人主页&#xff1a;谁在夜里看海. &#x1f4d6; 个人专栏&#xff1a;《C系列》《Linux系列》《算法系列》 ⛰️ 一念既出&#xff0c;万山无阻 目录 &#x1f4d6;一、算法思想 细节问题 &#x1f4da;左右临界 &#x1f4da;中点选择 &#x1f4da;…

[CTF/网络安全] 攻防世界 upload1 解题详析

[CTF/网络安全] 攻防世界 upload1 解题详析 考察文件上传&#xff0c;具体原理及姿势不再赘述。 姿势 在txt中写入一句话木马<?php eval($_POST[qiu]);?> 回显如下&#xff1a; 查看源代码&#xff1a; Array.prototype.contains function (obj) { var i this.…

网络安全运行与维护 加固练习题

1. 提交用户密码的最小长度要求。 输入代码: cat /etc/pam.d/common-password 提交答案: flag{20} 2.提交iptables配置以允许10.0.0.0/24网段访问22端口的命令。 输入代码: iptables -A INPUT -p tcp -s 10.0.0.0/24 --dport 22 -j ACCEPT 提交答案: flag{iptables -A I…

PID模糊控制算法(附MATLAB仿真程序)

一、基本原理 PID模糊控制算法是一种将传统PID控制与模糊逻辑相结合的控制策略。它利用模糊逻辑处理不确定性和非线性问题的能力&#xff0c;以提高控制系统的性能。以下是PID模糊控制算法的基本原理&#xff1a; 1.1. **误差和误差变化率的计算**&#xff1a; - 首先&…

【leetcode100】螺旋矩阵

1、题目描述 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5] 2、初始思路 2.1 思路 定义上下左右…

2024.11.29(单链表)

思维导图 声明文件 #ifndef __LINKLIST_H__ #define __LINKLIST_H__#include <myhead.h>typedef char datatype; //数据元素类型 //定义节点类型 typedef struct Node {union{int len; //头节点数据域datatype data; //普通节点数据域};struct Node *next; //指针域…

第六届金盾信安杯-SSRF

操作内容&#xff1a; 进入环境 可以查询网站信息 查询环境url https://114.55.67.167:52263/flag.php 返回 flag 就在这 https://114.55.67.167:52263/flag.php 把这个转换成短连接&#xff0c;然后再提交 得出 flag

【Linux】进程控制,手搓简洁版shell

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;Linux 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 1、进程创建2、进程终止3、进程等待4、进程程序替换5、手写简洁版shell 1、进程创建 fork函数&#xff1a;从已经存在的进程中创…

逆向攻防世界CTF系列42-reverse_re3

逆向攻防世界CTF系列42-reverse_re3 参考&#xff1a;CTF-reverse-reverse_re3&#xff08;全网最详细wp&#xff0c;超4000字有效解析&#xff09;_ctfreverse题目-CSDN博客 64位无壳 _int64 __fastcall main(__int64 a1, char **a2, char **a3) {int v4; // [rsp4h] [rbp-…

安装 RabbitMQ 服务

安装 RabbitMQ 服务 一. RabbitMQ 需要依赖 Erlang/OTP 环境 (1) 先去 RabbitMQ 官网&#xff0c;查看 RabbitMQ 需要的 Erlang 支持&#xff1a;https://www.rabbitmq.com/ 进入官网&#xff0c;在 Docs -> Install and Upgrade -> Erlang Version Requirements (2) …

ECharts柱状图-交错正负轴标签,附视频讲解与代码下载

引言&#xff1a; 在数据可视化的世界里&#xff0c;ECharts凭借其丰富的图表类型和强大的配置能力&#xff0c;成为了众多开发者的首选。今天&#xff0c;我将带大家一起实现一个柱状图图表&#xff0c;通过该图表我们可以直观地展示和分析数据。此外&#xff0c;我还将提供…

Scala关于成绩的常规操作

score.txt中的数据&#xff1a; 姓名&#xff0c;语文&#xff0c;数学&#xff0c;英语 张伟&#xff0c;87&#xff0c;92&#xff0c;88 李娜&#xff0c;90&#xff0c;85&#xff0c;95 王强&#xff0c;78&#xff0c;90&#xff0c;82 赵敏&#xff0c;92&#xff0c;8…

【机器学习】入门机器学习:从理论到代码实践

我的个人主页 我的领域&#xff1a;人工智能篇&#xff0c;希望能帮助到大家&#xff01;&#xff01;&#xff01;点赞❤ 收藏❤ 机器学习&#xff08;Machine Learning&#xff09;是人工智能的一个分支&#xff0c;它通过算法从数据中学习规律&#xff0c;并基于这些规律进行…

Spring Web开发(请求)获取JOSN对象| 获取数据(Header)

大家好&#xff0c;我叫小帅今天我们来继续Spring Boot的内容。 文章目录 1. 获取JSON对象2. 获取URL中参数PathVariable3.上传⽂件RequestPart3. 获取Cookie/Session3.1 获取和设置Cookie3.1.1传统获取Cookie3.1.2简洁获取Cookie 3. 2 获取和存储Session3.2.1获取Session&…

[Deep Learning] 深度学习中常用函数的整理与介绍(pytorch为例)

文章目录 深度学习中常用函数的整理与介绍常见损失函数1. L2_loss | nn.MSELoss()公式表示&#xff1a;特点&#xff1a;应用&#xff1a;缺点&#xff1a;主要参数&#xff1a;示例用法&#xff1a;注意事项&#xff1a; 2. L1 Loss | nn.L1Loss数学定义&#xff1a;特点&…