【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.mzph.cn/pingmian/62637.shtml

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

相关文章

# 01_Python基础到实战一飞冲天(三)--python面向对象(一)--简单类

01_Python基础到实战一飞冲天&#xff08;三&#xff09;–python面向对象&#xff08;一&#xff09;–简单类 一、面向对象-01-基本概念 1、面向对象(OOP) 面向对象编程 —— Object Oriented Programming 简写 OOP。 2、面向对象(OOP) 学习目标 了解 面向对象 基本概念…

Java 基础知识与核心概念

Java 作为一门广泛使用的编程语言&#xff0c;它的基础知识是每个开发者必须掌握的。无论是面向对象编程&#xff08;OOP&#xff09;还是集合框架的使用&#xff0c;理解这些核心概念能够帮助我们在日常开发中更加高效和准确地编写代码。本文将从设计模式、集合原理到常见类的…

【C++习题】24.二分查找算法_0~n-1中缺失的数字

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;图解 题目链接&#xff1a; 剑指 Offer 53 - II. 0&#xff5e;n-1中缺失的数字 题目描述&#xff1a; 解法 哈希表&#xff1a; 建立一个hash表看哪个数字出现次数为0 直接遍历找结果&#xff1…

(即插即用模块-Convolution部分) 一、(ICLR 2022) ODConv 全维动态卷积

文章目录 1、Omni-dimensional Dynamic Convolution2、代码实现 paper&#xff1a;OMNI-DIMENSIONAL DYNAMIC CONVOLUTION Code&#xff1a;https://github.com/OSVAI/ODConv 1、Omni-dimensional Dynamic Convolution 论文首先分析了现有动态卷积的局限性&#xff0c;论文指出…

深度学习Python基础(2)

二 数据处理 一般来说PyTorch中深度学习训练的流程是这样的&#xff1a; 1. 创建Dateset 2. Dataset传递给DataLoader 3. DataLoader迭代产生训练数据提供给模型 对应的一般都会有这三部分代码 # 创建Dateset(可以自定义) dataset face_dataset # Dataset部分自定义过的…

(超详细图文详情)Navicat 配置连接 Oracle

1、下载依赖文件 Oracle官网下载直链&#xff1a;https://www.oracle.com/database/technologies/instant-client/winx64-64-downloads.html 夸克网盘下载&#xff08;oracle19c版本&#xff09;&#xff1a;https://pan.quark.cn/s/5061e690debc 官网下载选择对应 Oracle 版…

jdk各个版本介绍

Java Development Kit&#xff08;JDK&#xff09;是Java平台的核心组件&#xff0c;它包含了Java编程语言、Java虚拟机&#xff08;JVM&#xff09;、Java类库以及用于编译、调试和运行Java应用程序的工具。 JDK 1.0-1.4&#xff08;经典时代&#xff09; • JDK 1.0&#xff…

二分法篇——于上下边界的扭转压缩间,窥见正解辉映之光(1)

前言 二分法&#xff0c;这一看似简单却又充满哲理的算法&#xff0c;犹如一道精巧的数学之门&#xff0c;带领我们在问题的迷雾中找到清晰的道路。它的名字虽简单&#xff0c;却深藏着智慧的光辉。在科学的浩瀚星空中&#xff0c;二分法如一颗璀璨的星辰&#xff0c;指引着我们…

基于 FFmpeg/Scrcpy 框架构建的一款高性能的安卓设备投屏管理工具-供大家学习研究参考

支持的投屏方式有:USB,WIFIADB,OTG,投屏之前需要开启开发者选项里面的USB调试。 主要功能有: 1.支持单个或多个设备投屏。 2.支持键鼠操控。 3.支持文字输入。 4.支持共享剪切板(可复制粘贴电脑端文字到手机端,也可导出手机剪切板到电脑端)。 5.支持视频图片上传,可单…

【论文笔记】A Token-level Contrastive Framework for Sign Language Translation

&#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平。 基本信息 标题: A Token-level Contrastiv…

ROS2教程 - 3 HelloWorld

更好的阅读体验&#xff1a;https://www.foooor.com 3 HelloWorld 下面从 HelloWorld 开始&#xff0c;讲解 ROS2 的开发。 ROS 开发主要使用 C 或 Python 实现&#xff0c;如果要实现的功能&#xff0c;对性能有要求&#xff0c;可以使用 C 实现&#xff0c;如果对性能没有…

洛谷 B3626 跳跃机器人 C语言 记忆化搜索

题目&#xff1a; https://www.luogu.com.cn/problem/B3626 题目描述 地上有一排格子&#xff0c;共 n 个位置。机器猫站在第一个格子上&#xff0c;需要取第 n 个格子里的东西。 机器猫当然不愿意自己跑过去&#xff0c;所以机器猫从口袋里掏出了一个机器人&#xff01;这…

【AI】Sklearn

长期更新&#xff0c;建议关注、收藏、点赞。 友情链接&#xff1a; AI中的数学_线代微积分概率论最优化 Python numpy_pandas_matplotlib_spicy 建议路线&#xff1a;机器学习->深度学习->强化学习 目录 预处理模型选择分类实例&#xff1a; 二分类比赛 网格搜索实例&…

⭐️ GitHub Star 数量前十的工作流项目

文章开始前&#xff0c;我们先做个小调查&#xff1a;在日常工作中&#xff0c;你会使用自动化工作流工具吗&#xff1f;&#x1f64b; 事实上&#xff0c;工作流工具已经变成了提升效率的关键。其实在此之前我们已经写过一篇博客&#xff0c;跟大家分享五个好用的工作流工具。…

Tree搜索二叉树、map和set_数据结构

数据结构专栏 如烟花般绚烂却又稍纵即逝的个人主页 本章讲述数据结构中搜索二叉树与HashMap的学习&#xff0c;感谢大家的支持&#xff01;欢迎大家踊跃评论&#xff0c;感谢大佬们的支持! 目录 搜索二叉树的概念二叉树搜索模拟实现搜索二叉树查找搜索二叉树插入搜索二叉树删除…

Swift实现高效链表排序:一步步解读

文章目录 前言摘要问题描述题解解题思路Swift 实现代码代码分析示例测试与结果 时间复杂度空间复杂度总结关于我们 前言 本题由于没有合适答案为以往遗留问题&#xff0c;最近有时间将以往遗留问题一一完善。 148. 排序链表 不积跬步&#xff0c;无以至千里&#xff1b;不积小流…

【开篇】.NET开源 ORM 框架 SqlSugar 系列

.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列【数据事务…

qt QAnimationDriver详解

1、概述 QAnimationDriver是Qt框架中提供的一个类&#xff0c;它主要用于自定义动画帧的时间控制和更新。通过继承和实现QAnimationDriver&#xff0c;开发者可以精确控制动画的时间步长和更新逻辑&#xff0c;从而实现丰富和灵活的动画效果。QAnimationDriver与QAbstractAnim…

何时在 SQL 中使用 CHAR、VARCHAR 和 VARCHAR(MAX)

在管理数据库表时&#xff0c;考虑 CHAR、VARCHAR 和 VARCHAR(MAX) 是必不可少的。此外&#xff0c;使用正确的工具&#xff08;例如dbForge Studio for SQL Server&#xff09; &#xff0c;与数据库相关的任务都会变得更加容易。它是针对 SQL Server 专业人员的强大的一体化解…

20241127 给typecho文章编辑附件 添加视频 图片预览

Typecho在写文章时&#xff0c;如果一次性上传太多张图片可能分不清哪张&#xff0c;因为附件没有略缩图&#xff0c;无法实时阅览图片&#xff0c;给文章插入图片时很不方便。 编辑admin/file-upload.php 大约十八行的位置 一个while 循环里面,这是在进行html元素更新操作,在合…