【问题标题】:how does rust move semantics actually workrust 移动语义实际上是如何工作的
【发布时间】:2021-11-19 00:06:24
【问题描述】:

在写了一些代码和阅读了一些文章之后,我对 rust 中的移动语义有点困惑,我想在值移动之后,应该释放它,应该使内存无效。所以我试着写一些代码来作证。

第一个例子

#[derive(Debug)]
struct Hello {
    field: u64,
    field_ptr: *const u64,
}

impl Hello {
    fn new() -> Self {
        let h = Hello {
            field: 100,
            field_ptr: std::ptr::null(),
        };
        h
    }

    fn init(&mut self) {
        self.field_ptr = &self.field as *const u64;
    }
}
fn main(){
    let mut h = Hello::new();
    h.init();
    println!("=================");
    println!("addr of h: ({:?}) \naddr of field ({:?})\nfield_ptr: ({:?}) \nptr value {:?}", &h as *const Hello, &h.field as *const u64, h.field_ptr, unsafe {*h.field_ptr});

    let c = &h.field as *const u64;
    let e = &h as *const Hello;
    let a = h;
    let d = &a.field as *const u64;

    println!("=================");
    println!("addr of a: ({:?}) \naddr of field ({:?})\nfield_ptr: ({:?}) \nptr value {:?}", &a as *const Hello, &a.field as *const u64, a.field_ptr, unsafe {*a.field_ptr});
    println!("=================");
    println!("addr of c {:?}\nvalue {:?}", c, unsafe {*c});
    println!("addr of d {:?}\nvalue {:?}", d, unsafe {*d});
    println!("addr of e {:?}\nvalue {:?}", e, unsafe {&*e});
}

上面代码的结果是

=================
addr of h: (0x7ffee9700628) 
addr of field (0x7ffee9700628)
field_ptr: (0x7ffee9700628) 
ptr value 100
=================
addr of a: (0x7ffee9700720) 
addr of field (0x7ffee9700720)
field_ptr: (0x7ffee9700628) 
ptr value 100
=================
addr of c 0x7ffee9700628
value 100
addr of d 0x7ffee9700720
value 100
addr of e 0x7ffee9700628
value Hello { field: 100, field_ptr: 0x7ffee9700628 }

所以,我创建了一个自引用结构Hello 并使field_ptr 指向u64 字段,并使用原始点来保存结构的地址和字段的地址,并将h 移动到a 以使h 变量,但我仍然可以通过原始点获取 IMO 不应该存在的原始变量的值?

第二个例子

struct Boxed {
    field: u64,
}
fn main(){

   let mut f = std::ptr::null();
    {
        let boxed = Box::new(Boxed{field: 123});
        f = &boxed.field as *const u64;
    }
    println!("addr of f {:?}\nvalue {:?}", f, unsafe {&*f});
}

结果

addr of f 0x7fc1f8c05d30
value 123

我创建一个装箱值并在使用原始点保存它的地址后将其删除,我仍然可以通过原始点读取它的字段值。

所以我的困惑是

  1. rust 中的移动实际上是 memcpy 吗?而原始变量只是被编译器“隐藏”了?
  2. rust 何时真正释放堆上变量的内存? (第二个例子)

谢谢

我读过的 How does Rust provide move semantics?

【问题讨论】:

  • 请参阅hotel analogy answer,了解为什么在数据被释放后访问数据看起来仍然有效。在 Miri 下运行您的第二个示例会捕获这个 use-after-free 错误,尽管我有点失望它没有捕获第一个案例。
  • #1 是的,move 是伴随所有权转移的 memcpy - 即旧所有者无法访问该值,而新所有者负责释放它。当然,根据优化,可能会消除复制。 #2 当值超出范围并调用Drop::drop() 时,内存被释放。这并不意味着内存被清理了,它只是意味着它被标记为空闲以供将来分配(或者,更罕见的是,返回给操作系统以供其他程序使用)。
  • @user4815162342 感谢您的回答,这回答了我的问题。

标签: rust


【解决方案1】:

所以你的输出的第一块应该是清楚的,对吧?结构的地址只是该结构在内存中所在位置的内存的第一位,与它的第一个字段的地址相同。

现在是您的第二个区块。您将一些原始指针抓取到结构中,然后通过let a = h 移动结构。

这样做是:在堆栈上,我们现在有了一个新变量a,它是变量h 的旧堆栈布局的内存副本。这就是a 和a.field 都有一个新地址的原因。当然,原始指针仍然指向旧的h.field 地址,这就是您仍然可以访问该数据的原因。

请注意,您只能通过 unsafe 块执行此操作,因为您所做的 不安全。没有保证无论你的字段指针指向什么都将保持有效。

如果您删除所有使用 unsafe 构造,将无法通过 h.field 访问 a.field。

同样的想法也适用于第二个例子。如果你不使用原始指针和不安全的块,你就无法得到丢弃的东西,那是因为这段代码非常可疑。在您的简单示例中,它仍然有效,因为 Rust 不只是继续打乱已删除值的内存。除非您的程序中的其他东西重新利用了该内存,否则它将保持您离开它的方式。

【讨论】:

  • “在您的简单示例中,它仍然有效,因为 Rust 不只是继续打乱已删除值的内存。”我认为这更像是 LLVM 的事情:从技术上讲,它应该能够在分配堆栈帧时考虑临时变化(因此如果它们的内容不再有效,则重用现有的堆栈槽),但实际上它不会打扰 orRust 不会将变量标记为已死,因此 llvm 必须假设它们在帧结束之前都处于活动状态。
猜你喜欢
  • 2016-03-21
  • 1970-01-01
  • 2015-06-11
  • 2021-01-21
  • 2011-09-27
  • 2021-12-16
  • 2013-03-14
  • 2021-03-23
  • 2011-02-11
相关资源
最近更新 更多