Rust: 关于Pin以及move前后分析

一、Pin由来

在Rust中,自引用结构会导致,此变量被move后,其内部自引用的指针指向不改变,从而存在安全隐患 。

注意:Pin是一个struct。UnPin和!UnPin是trait。这个要分清。

二、方案

对原始的自引用结构,如何增加安全性,方案有以下两步:

1、里面加Phantompinned,

2、外面套上Pin,

这样新的结构被move后,可以保证里面的自引用的指针指向的内容正确。

基于以上分析,我们来进行相关验证:

1、验证普通的结构体move前后行为:move的行为会产生什么影响?

2、带自引用结构move前后的行为:是否存在安全隐患?

3、带PhantomPinned自引用结构move前后的行为:是否解决了原先产生的问题?

在行为方面,具体的考察在于指针地址,指针指向内容的变化,是否能揭示相关问题。

三、相关代码

因为不涉及外部库,cargo.toml文件不需要另列。main.rs代码如下:

bash 复制代码
use std::pin::Pin;
use std::marker::PhantomPinned;

// unpin结构,没有自引用
#[derive(Debug,Default)]
struct Free{
    content: String,
}

// unpin结构 =>move后,ptr对应的指针还会指向原来的地址,但不能正确指向content内容
#[derive(Debug)]
struct SelfRef {
    content: String,
    _ptr: *const String,//对应content 或者是&String
}
impl SelfRef{
    fn default() ->Self{
        SelfRef{
            content: String::from("hello world!"),
            _ptr: std::ptr::null(),
        }
    }
    fn set_ref_value(&mut self){
        self._ptr = & self.content as *const String; 
    }
}
// !unpin结构,通过引入phantompinned,再pin后,SelfRefPinned被move后,_ptr对应指针可以正确指向content
#[derive(Debug)]
struct SelfRefPinned {
    content: String,
    _ptr: *const String,//对应content
    _marker: PhantomPinned,
}

impl SelfRefPinned{
    fn default() ->Self{
        SelfRefPinned{
            content: String::from("hello world!"),
            _ptr: std::ptr::null(),
            _marker: PhantomPinned,
        }
    }
    fn set_ref_value(pinned_obj: Pin<&mut Self>){
        let content_ptr = &pinned_obj.content as *const String;
        let mut_self_ref: &mut SelfRefPinned = unsafe { pinned_obj.get_unchecked_mut() };
        mut_self_ref._ptr = content_ptr;
    }
}
fn main() {
    let _free = Free::default();
    let mut _self_ref = SelfRef::default();
    _self_ref.set_ref_value();
    let mut self_ref_pin = SelfRefPinned::default();
    let mut _self_ref_pinned = unsafe { Pin::new_unchecked(&mut self_ref_pin) };
    SelfRefPinned::set_ref_value(_self_ref_pinned.as_mut());

    // Free before move
    println!("-----------before move-------------");
    println!("_free content:{:?}  content 内存指针: {:p}",_free.content,&_free.content);
    // Free after move
    println!("-----------after move-------------");
    let free_ = _free; //第1次move操作
    println!("free_ content:{:?}  content 内存指针: {:p}",free_.content,&free_.content);
    // SelfRef before move 
    println!("-----------before move-------------");
    println!("_self_ref 内存地址:{:p} content内存地址:{:p}",&_self_ref,&_self_ref.content);
    println!("_self_ref._ptr对应value :{:?}  自引用指针的内存地址: {:?}",unsafe{&*_self_ref._ptr},_self_ref._ptr);
    // SelfRef after move  
    println!("-----------after move-------------");
    let self_ref_ = _self_ref; //第1次move操作
    println!("self_ref_ 内存地址:{:p} content内存地址:{:p}",&self_ref_,&self_ref_.content);
    println!("self_ref_._ptr对应value :{:?}  自引用指针的内存地址: {:?}",unsafe{&*self_ref_._ptr},self_ref_._ptr);
    
    // SelfRef  move again
    println!("-----------after move again -------------");
    let _self_ref_ = self_ref_; //第2次 move操作
    println!("_self_ref_ 内存地址:{:p} content内存地址:{:p}",&_self_ref_,&_self_ref_.content);
    println!("_self_ref_._ptr对应value :{:?}  自引用指针的内存地址: {:?}",unsafe{&*_self_ref_._ptr},_self_ref_._ptr);
    
    // SelfRefPinned before move
    println!("-----------before move-------------");
    println!("_self_ref_pinned 内存地址:{:p} content内存地址:{:p}",&_self_ref_pinned,&_self_ref_pinned.content);
    println!("_self_ref_pinned._ptr对应value :{:?}  自引用指针的内存地址: {:?}",unsafe{&*_self_ref_pinned._ptr},_self_ref_pinned._ptr);

    // SelfRefPinned after move
    println!("-----------after move-------------");
    let self_ref_pinned_ = _self_ref_pinned; //第1次move 操作
    println!("self_ref_pinned_ 内存地址:{:p} content内存地址:{:p}",&self_ref_pinned_,&self_ref_pinned_.content);
    println!("self_ref_pinned_._ptr对应value :{:?}  自引用指针的内存地址: {:?}",unsafe{&*self_ref_pinned_._ptr},self_ref_pinned_._ptr);

}

四、输出

bash 复制代码
-----------before move-------------
_free content:""  content 内存指针: 0x7ffd840875f0
-----------after move-------------
free_ content:""  content 内存指针: 0x7ffd840875d0
-----------before move-------------
_self_ref 内存地址:0x7ffd84087570 content内存地址:0x7ffd84087570
_self_ref._ptr对应value :"hello world!"  自引用指针的内存地址: 0x7ffd84087570
-----------after move-------------
self_ref_ 内存地址:0x7ffd84087590 content内存地址:0x7ffd84087590
self_ref_._ptr对应value :"hello world!"  自引用指针的内存地址: 0x7ffd84087570
-----------after move again -------------
_self_ref_ 内存地址:0x7ffd84087540 content内存地址:0x7ffd84087540
_self_ref_._ptr对应value :"hello world!"  自引用指针的内存地址: 0x7ffd84087570
-----------before move-------------
_self_ref_pinned 内存地址:0x7ffd84087530 content内存地址:0x7ffd840875b0
_self_ref_pinned._ptr对应value :"hello world!"  自引用指针的内存地址: 0x7ffd840875b0
-----------after move-------------
self_ref_pinned_ 内存地址:0x7ffd84087568 content内存地址:0x7ffd840875b0
self_ref_pinned_._ptr对应value :"hello world!"  自引用指针的内存地址: 0x7ffd840875b0

五、总结

1、几点观察

(1)从上面Free类型来看,move后,struct对象的内存地址有变动。

(2)自引用结构move后,可以看见:尽管对象的struct内存地址发生变化后,但对象中的content的地址也发生了新的变化,但对象中的自引用对应指针并没有变化(还是指向move前的对象的content地址),并没指向新对象content所对应的内存地址。这样,就存在了不一致的问题。

(3)PhantomPinned和Pin之后,在move后,尽管新struct对象的地址有变动,但新的对象的content的地址也没有变化,新的对象自引用指针也没有变动。因此,对象的自引对应的指针正确指向的content所对应的地址,之前的隐患消除。

2、相关问题

不知大家注意到没有,上面的代码,SelfRef对象被安排了2次move,但是经过2次move的对象,其自引用指针的内存还是指次第1次move前的对象的地址位置。如果多次move后,这些变量被drop后,原始的地址没清空或初始化,那么解引用就会出现"指引悬垂"的问题。

但是,在这些变量的地址没有被清空或初始化前,而且这个地址看似"还能"正确解引用出"正确"的值。一方面,造成了假象;另一方面,也把问题隐藏了起来,在很多情况下,看起来让程序在大部分的情况下运行结果表现正确。但其实已经造成潜在的安全隐患。

正因为此,异步的Future才需要通过Pin来堵住这个安全的漏洞。这个才是Pin的价值所在。

相关推荐
coding侠客12 分钟前
Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘
java·spring boot·后端
froginwe1114 分钟前
PostgreSQL表达式的类型
开发语言
委婉待续17 分钟前
java抽奖系统(八)
java·开发语言·状态模式
deja vu水中芭蕾19 分钟前
嵌入式C面试
c语言·开发语言
爱码小白20 分钟前
PyQt5 学习方法之悟道
开发语言·qt·学习方法
西猫雷婶40 分钟前
python学opencv|读取图像(十六)修改HSV图像HSV值
开发语言·python·opencv
weixin_537590451 小时前
《Java编程入门官方教程》第八章练习答案
java·开发语言·servlet
lsx2024061 小时前
MVC 发布
开发语言
qincjun1 小时前
文件I/O操作:C++
开发语言·c++