IT评测·应用市场-qidao123.com
标题:
rust学习二十.1、不安全代码之原始指针(裸指针)
[打印本页]
作者:
欢乐狗
时间:
2025-3-10 16:26
标题:
rust学习二十.1、不安全代码之原始指针(裸指针)
一、媒介
指针在前面的篇幅中已经先容过许多,但主要是智能指针。
智能指针管理堆上的数据,而且受到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++之类的语言。
四、示例
毫无疑问,现在对于原始指针以及内存操作并不熟悉,本文的例子基本上是来自,只不过部门稍作了一些调整。
此处示例主要关于:原始指针的创建和销毁
use std::ffi::CString;
use std::os::raw::c_char;
use std::slice;
fn main() {
//1.0 演示原始指针的创建和访问,以及可以同时拥有多个指向相同位置的原始指针(不论是可变还是不可变的原始指针)
// 以及如何解引用原始指针
let mut num = 5; //这里必须定义为mut 。但也发现了rust编译器的一个问题:会错误体提示要移除这个mut,但其实不能移除。
let r1 = &num as *const i32; // 不可变原始指针
let r2 = &mut num as *mut i32; // 可变原始指针
println!("r1: {}, r2: {}", unsafe { *r1 }, unsafe { *r2 });
//让r2+1
unsafe {
*r2 += 1;
}
println!("r1: {}, r2: {}", unsafe { *r1 }, unsafe { *r2 });
//2.0 创建指向任意内存地址的裸指针
// 此处代码需要注释掉,否则后续的代码不会执行。因为原始指针指向的内存地址是不合法的,所以会引发运行时错误。
//create_raw_pointer_from_address();
println!("---------------------------------------------------------------");
//3.0 使用Box创建原始指针
create_raw_pointer_use_box();
//4.0 如何创建一个空的原始指针
create_empty_raw_pointer();
//5.0 演示如果使用原始指针把一个数组切成两半,且每一半都是可变的切片
demo_split_slice_to_two_half();
}
/**
* 创建一个指向任意内存地址的原始指针
*/
#[allow(dead_code)]
fn create_raw_pointer_from_address() {
let address = 0x012345usize;
let r = address as *const i32;
unsafe {
println!("r: {}", *r);
}
}
/**
* 使用Box创建原始指针
*/
fn create_raw_pointer_use_box() {
//回忆下Box指针,我们知道Box指针是一个堆上分配的智能指针。
let boxed = Box::new(5);
let five = *boxed;
println!("five: {}", five);
let brave = Box::new(String::from("Rust"));
let raw_brave = Box::into_raw(brave);
unsafe{
println!("raw_brave: {:?}", *raw_brave);
}
//现在需要手动释放内存,否则会造成内存泄露
println!("释放raw_brave");
unsafe{
drop(Box::from_raw(raw_brave));
}
println!("释放raw_brave完成");
// unsafe{
// println!("raw_brave依然存在,但这个时候它应该是一个空的: {:?}", *raw_brave);
// }
}
/**
* 创建一个空的原始指针-利用std::ptr::null
*/
fn create_empty_raw_pointer() {
let mut ptr = std::ptr::null::<i32>();
if ptr.is_null() {
println!("指针ptr是空的");
} else {
println!("指针ptr不是空的");
}
let mut value = 5;
unsafe {
// 将指针指向一个有效的内存地址
ptr = &value as *const i32;
// 读取指针的值
if !ptr.is_null() {
println!("指针ptr现在的值是: {}", *ptr);
} else {
println!("指针ptr现在是空的");
}
}
}
/**
* 演示如果使用原始指针把一个数组切成两半,且每一半都是可变的切片
*/
fn demo_split_slice_to_two_half() {
let mut scores=[10,20,30,40,50];
println!("原始scores: {:?}", scores);
let (left, right) = unsafe_slice(&mut scores, 3);
println!("scores-left: {:?}, scores-right: {:?}", left, right);
//现在改改左边第一个,看是不是发生了效果
left[0] = 1024;
right[0] = 1975;
println!("修改后scores: {:?}", scores); //事实证明了这点:左边改了,所以可变切片可以的
let mut poems=["独怜幽草涧边生","上有黄丽深树鸣","春潮带雨晚来急"];
let (left, right) = unsafe_slice(&mut poems, 2);
println!("left: {:?}, right: {:?}", left, right);
}
/**
* 使用原始指针创建切片
* 这里有非常关键的说明:(来自书本)
* Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同部分:它只知道我们借用了同一个 slice 两次。
* 本质上借用 slice 的不同部分是可以的,因为结果两个 slice 不会重叠,不过 Rust 还没有智能到能够理解这些
*
* 这一段至少说明了:rust的编译器也不是万能的,要是万能就太慢了。
* 在rust书中,此例子属于:创建不安全代码的安全抽象 。
* 即把不安全的操作封装起来,对外提供安全的接口(函数)
* @param values 实际是一个数组的完整切片
* @param mid 切片中间的索引位置
*/
fn unsafe_slice<T>(values: &mut [T], mid: usize)-> (&mut [T], &mut [T]) {
//let mut data = [1, 2, 3];
//let ptr = &data as *const _;
let len = values.len();
let ptr = values.as_mut_ptr(); // 切片转为原始指针,且指向切片的起始位置。
//方法from_raw_parts_mut用于从原始指针和长度创建一个可变切片。
//注意:这里的add方法,是原始指针的一个方法,用于计算偏移后的新地址。
//如果切片中保存的是汉字,那么应该加多少了?
unsafe {
(
slice::from_raw_parts_mut(ptr, mid),
slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}
复制代码
输出如下:
有几点值得说:
不安全代码必须写在unsafe{}代码块中,否则会报错。
rust的编译器偶然候会有问题
原始指针原则上必要自己手动开释,但也不是所有的都必要。如果是借用来的,可以不要;如果是Box创建的,可以开释
随意访问内存一块区域,可能会导致步伐停止
五、小结
作为一个告急的补充,原始指针为rust编程提供了机动性,能够做安全指针所不能完成的一些工作,主要是内存操作和外部函数接口、高级数据结构等
rust有多种方式可以创建原始指针,包括使用引用、Box指针创建、空指针创建
部门原始指针必要手动开释,部门不必要,主要看如何定义,这个必要特别留意
原始指针的存在,再一次证明了个性化的、简单的东西能够更加高效,能力也更强,只是对于使用者的水平要求较高
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/)
Powered by Discuz! X3.4