【问题标题】:What happens to an allocation when the variable containing it is reassigned? [duplicate]当包含它的变量被重新分配时,分配会发生什么? [复制]
【发布时间】:2018-02-08 20:18:49
【问题描述】:

我很确定它有一个专门的名称,但我不知道它是什么。

当你有一个停止指向一个有效对象的指针时,这就是一个悬空指针,但是一个没有对它的引用的对象呢,尤其是在 Rust 中?

以如下代码为例:

{ 
    let mut v: Vec<u32> = vec![1, 2, 3];
    v = Vec::new();
    v.push(0);
}

v 被重新分配给一个全新的向量时,旧向量会发生什么?在 C/C++ 中,这是内存泄漏的诞生,因为没有人会释放该内存,而您也不再有办法这样做。然而,在 Rust 中,退出作用域时会发生各种魔法(因此示例代码中的 {}

从逻辑的角度来看,由于 Rust 没有 GC,这将表明向量只保留在内存中直到进程终止,当超出范围时扫描无法访问的对象会影响实际的 GC,但我没有但对 Rust 的内部结构有足够的了解,可以做出这些猜测(尽管我想在某个时候这样做)。

上面的代码到底发生了什么?是不是像在 C/C++ 中一样需要注意内存泄漏?

【问题讨论】:

  • "在 C/C++ 中,这是内存泄漏的诞生,因为没有人会释放该内存,而您也没有办法这样做。"在 C++ 中,vector::operator= 将释放旧内存,因此不会有泄漏。在 C 中没有向量,您根本无法分配给数组,因此您将无法编写这样的代码。
  • operator= 的正确实现应该始终正确处理内存。如果没有,请远离这种类型。
  • 所有不是使用new 直接创建或损坏的东西,都会在 C++ 中自行清理。要创建内存泄漏,您必须使用指向动态分配内存的原始指针。一旦你意识到这一点并尝试在 Rust 中做同样的事情,你会发现 Rust 不允许你触摸 unsafe 块之外的原始指针。
  • 建议的重复目标演示了阴影,而不是覆盖。这种情况下的行为是不同的(比较12
  • @trentcl 在你的第二个例子中(我相信它代表了阴影)——看起来内存被保留在堆栈上(未释放),直到modified 2 中的范围结束。所以如果你运行一个大循环——这种方法可能会积累相当大的“临时内存泄漏”,对吧?

标签: memory memory-management memory-leaks scope rust


【解决方案1】:

Rust(就像 C++ 一样)使用 RAII 来处理这个问题。需要销毁的类型实现 Drop 特征(在 C++ 中,它们有一个析构函数),当变量超出范围时会自动调用它。

在锈中:

{
    let a = foo();
    a = b; // `b` is moved to `a`,
           // the previous value of `a` is dropped, which frees memory
} // `a` goes out of scope, it is dropped, which frees memory

在 C++ 中:

{
    auto a = foo();
    a = b; // `=` is essentially a function call,
           // but a proper `=` implementation should handle memory properly
} // `a` goes out of scope, its destructor is called, which frees memory

对于在 Rust 或 C++ 中正确实现的类型,没有内存泄漏。 另一方面,C 没有这样的概念,内存总是必须显式释放。

【讨论】:

  • 我明白了,这很有道理!我在这里继续直觉,但我猜你所说的,以及 Rust 的所有权概念以及如果你最终得到悬空指针,编译器会如何向你咆哮,这几乎可以确保内存安全。但是,如果变量在顶层怎么办?就像main 函数中的第一个变量一样?这意味着它只会在程序终止时超出范围
  • @RaresDima a 的旧值在分配新值之前被删除,与a 的范围无关。这个答案有点不完整,因为它只提到drop() 在变量超出范围时被调用。 Rust 编译器在许多其他地方插入隐式删除,例如就在赋值之前,或者如果一个变量仅在一个if 分支中被移出,它会被隐式地删除到另一个if 分支中。甚至存在编译器无法静态解决的情况,因此使用称为“drop flags”的变量来跟踪是否需要删除变量。
  • 另见the chapter on drop flags in the Rustonomicon。另请注意,C++ 中的 RAII 实际上只在作用域末尾调用析构函数。在其他情况下(如赋值),operator= 的实现负责清理,而不是编译器的静态分析。简单地说“Rust(就像 C++)使用 RAII 来处理这个问题”可能是准确的(不确定术语),但这也有点误导,因为 Rust 和 C++ 中的 conecpt 实现完全不同。
猜你喜欢
  • 2014-12-21
  • 2016-04-29
  • 1970-01-01
  • 1970-01-01
  • 2015-02-14
  • 1970-01-01
  • 2013-06-22
  • 2017-09-15
  • 1970-01-01
相关资源
最近更新 更多