下面的代码运行时可能出错,或者能运行打印出:
origin before assignment "aa"
origin after assignment "lP"
cloned "bb"
可以看到变量 origin
指针指向的内容在执行 *(cloned.ptr) = "bb".to_string();
之后发生了改变,但是为什么会这样呢?
use std::ptr::{self};
use std::{
alloc::{alloc, Layout},
fmt::{Debug, Display},
};
#[derive(PartialEq)]
pub struct ManualBox2<T> {
ptr: *mut T,
layout: Layout,
}
impl<T> ManualBox2<T> {
pub fn new(value: T) -> Self {
let layout = Layout::new::<T>();
let ptr = unsafe { alloc(layout) as *mut T };
unsafe { ptr.write(value) };
Self { ptr, layout }
}
}
impl<T: Display> Display for ManualBox2<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", unsafe { &(*self.ptr) })
}
}
impl<T: Debug> Debug for ManualBox2<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", unsafe { &(*self.ptr) })
}
}
impl<T> Clone for ManualBox2<T> {
fn clone(&self) -> Self {
let layout = self.layout;
let new_prt = unsafe { alloc(layout) as *mut T };
unsafe {
ptr::copy_nonoverlapping(self.ptr, new_prt, 1);
}
Self {
ptr: new_prt,
layout,
}
}
}
fn main() {
let origin = ManualBox2::new("aa".to_string());
let cloned = origin.clone();
println!("origin before assignment {:?}", origin);
unsafe {
*(cloned.ptr) = "bb".to_string();
}
println!("origin after assignment {:?}", origin);
println!("cloned {:?}", cloned);
}
1
共 3 条评论, 1 页
评论区
写评论谢谢各位大佬!!
需要注意的是,
String
结构体的布局等同于Vec<u8>
相当于也是一个胖指针,你在clone
函数里通过unsafe
实现的,只是String
的浅拷贝,即cloned
底层的字符串和origin
指向的都是堆上同一块内存。所以在
cloned
被覆盖赋值时,原String
会被销毁,底层的堆空间也一并释放,导致origin
的字符串底层指针成了悬垂野指针,出现未定义行为:当野指针未知空间的
byte
可以被utf-8
编码时,就是强制识别为字符串序列然后正常打印; 编码错误时终端会拒绝输出然后出现运行时异常does not support writing non-UTF-8 byte sequences
。你的 Clone 实现有些问题,直接复制 T 是可能导致 UB 的,试想一下如果 T 存有指针(比如说,T = String)……
读到的数据有问题是因为标准库文档中关于 pointer 这一节有写:
cloned 里存的也是 "aa" 这个 String,所以 "aa" 这个 String 在
*(cloned.ptr) = "bb".to_string()
就 Drop 掉了,之后再通过 origin 读就是 use-after-free 了。题外话,Rust 标准库文档里有很多核心语言(就比如这里有关裸指针的内容)相关的文档,我总觉得官方的那本 "The Book" 应该多链接一些标准库文档,类似于“有兴趣的读者可以移步这里”这样的。