< 返回版块

NenX 发表于 2025-06-10 17:19

下面的代码运行时可能出错,或者能运行打印出:

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);
}



代码地址

评论区

写评论
作者 NenX 2025-06-11 09:10

谢谢各位大佬!!

xiaoyaou 2025-06-11 00:34

需要注意的是,String结构体的布局等同于Vec<u8>相当于也是一个胖指针,你在clone函数里通过unsafe实现的,只是String的浅拷贝,即cloned底层的字符串和origin指向的都是堆上同一块内存。

所以在cloned被覆盖赋值时,原String会被销毁,底层的堆空间也一并释放,导致origin的字符串底层指针成了悬垂野指针,出现未定义行为:

当野指针未知空间的byte可以被utf-8编码时,就是强制识别为字符串序列然后正常打印; 编码错误时终端会拒绝输出然后出现运行时异常does not support writing non-UTF-8 byte sequences

TinusgragLin 2025-06-10 20:05

你的 Clone 实现有些问题,直接复制 T 是可能导致 UB 的,试想一下如果 T 存有指针(比如说,T = String)……

读到的数据有问题是因为标准库文档中关于 pointer 这一节有写:

Storing through a raw pointer using *ptr = data calls drop on the old value

cloned 里存的也是 "aa" 这个 String,所以 "aa" 这个 String 在 *(cloned.ptr) = "bb".to_string() 就 Drop 掉了,之后再通过 origin 读就是 use-after-free 了。

题外话,Rust 标准库文档里有很多核心语言(就比如这里有关裸指针的内容)相关的文档,我总觉得官方的那本 "The Book" 应该多链接一些标准库文档,类似于“有兴趣的读者可以移步这里”这样的。

1 共 3 条评论, 1 页