一个变量在被删除后如何被释放?

How can a variable be deallocated after it is dropped?

我正在通读 "learn rust with entirely too many linked lists"。 在第 2.7 章中,作者说“我们不能在解除分配后删除 Box 的内容”。值被删除后如何重新分配?释放不会发生在 drop 的实现中,还是这些独立的概念?

对于某些上下文,作者正在解释为什么链表的特定实现不是尾递归的,因此不能被编译器优化。

链表:

pub struct List {
    head: Link,
}

enum Link {
    Empty,
    More(Box<Node>),
}

struct Node {
    elem: i32,
    next: Link,
}

放弃实施:

impl Drop for List {
    fn drop(&mut self) {
        // NOTE: you can't actually explicitly call `drop` in real Rust code;
        // we're pretending to be the compiler!
        self.head.drop(); // tail recursive - good!
    }
}

impl Drop for Link {
    fn drop(&mut self) {
        match *self {
            Link::Empty => {} // Done!
            Link::More(ref mut boxed_node) => {
                boxed_node.drop(); // tail recursive - good!
            }
        }
    }
}

impl Drop for Box<Node> {
    fn drop(&mut self) {
        self.ptr.drop(); // uh oh, not tail recursive!
        deallocate(self.ptr);
    }
}

impl Drop for Node {
    fn drop(&mut self) {
        self.next.drop();
    }
}

tail-recursion 在 impl Drop for Box<Node> 上的问题是我们必须 self.ptr.drop() 然后 然后 deallocate(self.ptr); tail-recursion 会要求我们 deallocate(self.ptr) 然后 然后 self.ptr.drop() (所以 drop() 在函数的尾部位置递归)。

问题是,当我们首先 deallocate(self.ptr) 时,self.ptr 的内容立即处于未定义状态,因为它是已释放的内存。如果我们然后 self.ptr.drop(),我们将需要 &mut Box<Node> 来执行 drop() 调用(显示为 &mut self)。

这不仅意味着析构函数可以有效地访问已释放的内存。这也意味着我们必须以 &mut Box<Node> 的形式创建一个 reference 指向处于未定义状态的内存。在 Rust 中,这将是 Undefined Behavior,因为 Rust 对 reference 指向的内存提供非常 严格保证。

Wouldn't the deallocation occur in the implementation of drop or are these separate concepts?

它们是不同的概念。在示例代码中,Drop表示“清理我的内容/拥有的资源”,deallocate表示“return 我实际存储到池中的内存"。

drop 方法是 运行 对现有实例的引用。 The definition of drop actually says:

When this method has been called, self has not yet been deallocated. That only happens after the method is over. If this wasn’t the case, self would be a dangling reference.

因此,head 必须 完全删除才能重新分配。并且drop过程需要先drop掉链表中的所有节点,每个节点在drop完成后才会释放关联的节点,导致drop个栈,每个都会 后跟一个 deallocate,但只有在堆栈上它们上面的所有 dropdeallocates 完成之后。由于在整个堆栈中调用 drop 之前无法执行单个 deallocate(此时 drop 用于尾节点 returns 和尾节点是第一个deallocated),无法实现tail-recursively.


Side-note:这与您在 C++ 中看到的问题完全相同,试图使用 std::shared_ptrstd::unique_ptr 作为 next 指针来实现链表。就像 Rust 一样,C++ 区分销毁(清理对象 的资源)和释放(释放 持有 对象的内存)。当 head unique_ptr 被清除时,在它可以释放内存之前它必须告诉“head + 1”清除它自己的 unique_ptr,这反过来又告诉“ head + 2" 清除其 unique_ptr 等等。在每种情况下,销毁(通过析构函数)必须先于释放(对于堆分配的东西,这可能通过 operator delete 发生,或者只是编译器收缩堆栈而不实际为堆栈分配的东西释放任何内存)。