一个变量在被删除后如何被释放?
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
,但只有在堆栈上它们上面的所有 drop
和 deallocates
完成之后。由于在整个堆栈中调用 drop
之前无法执行单个 deallocate
(此时 drop
用于尾节点 returns 和尾节点是第一个deallocated),无法实现tail-recursively.
Side-note:这与您在 C++ 中看到的问题完全相同,试图使用 std::shared_ptr
或 std::unique_ptr
作为 next
指针来实现链表。就像 Rust 一样,C++ 区分销毁(清理对象 内 的资源)和释放(释放 持有 对象的内存)。当 head
unique_ptr
被清除时,在它可以释放内存之前它必须告诉“head
+ 1”清除它自己的 unique_ptr
,这反过来又告诉“ head
+ 2" 清除其 unique_ptr
等等。在每种情况下,销毁(通过析构函数)必须先于释放(对于堆分配的东西,这可能通过 operator delete
发生,或者只是编译器收缩堆栈而不实际为堆栈分配的东西释放任何内存)。