【问题标题】:Understanding memory leakage with Rc in rust用 Rc 在 rust 中理解内存泄漏
【发布时间】:2020-04-13 11:37:02
【问题描述】:

如果我们看下面的代码(playground link):

use std::cell::RefCell;
use std::rc::Rc;

struct Person {
    name: String,
    parent: Option<Rc<RefCell<Person>>>,
    child: Option<Rc<RefCell<Person>>>,
}

impl Drop for Person {
    fn drop(&mut self) {
        println!("dropping {}", &self.name);
    }
}

pub fn main() {
    let anakin = Rc::new(RefCell::new(Person {
        parent: None,
        child: None,
        name: "anakin".to_owned(),
    }));

    let luke = Rc::new(RefCell::new(Person {
        parent: None,
        child: None,
        name: "luke".to_owned(),
    }));

    luke.borrow_mut().parent = Some(anakin.clone());
    anakin.borrow_mut().child = Some(luke.clone());

    println!("anakin {}", Rc::strong_count(&anakin));
    println!("luke {}", Rc::strong_count(&luke));
}

代码运行时,不会打印来自 drop 实现的消息,因为该代码应该会泄漏内存。

当代码完成后,luke 和 anakin 的计数应该以 1 而不是 0 结束(此时托管堆数据将被清理)。

为什么计数最终不为 0?我原以为会发生以下顺序:

  1. 我们从堆中的两个数据位置开始,一个由 luke 和 anakin.child 指向,另一个由 anakin 和 luke.parent 指向。对象 luke 拥有 luke.parent。对象 anakin 拥有 anakin.child

  2. luke 超出范围,这意味着它拥有的成员也被删除。所以卢克和卢克.parent 下降。两者都是 Rcs,因此两个内存位置的引用计数都下降到 1

  3. anakin 被丢弃,导致 Rc 对象 anakin 和 anakin.child 被丢弃,这导致两个内存位置的 ref 计数降至 0。

这是应该清理堆数据的地方吗?对象被丢弃的时候,它的成员是不是也被丢弃了?

当我通过borrow_mut 删除或注释连接luke 和anakin 的行时,删除按预期发生并且消息正确打印。

【问题讨论】:

    标签: memory memory-leaks rust


    【解决方案1】:
    1. luke 超出范围,这意味着它拥有的成员也被删除。所以卢克和卢克.parent 下降。两者都是 Rcs,因此两个内存位置的引用计数都下降到 1

    这是您误解的步骤。

    对象的成员在被丢弃的时候是不是也被丢弃了?

    没错。减少的计数是与 Rc 指向的数据直接相关的计数。当luke 超出范围,Rc 超出范围,并且引用lukeRefCell 的事物数量减少时,代码对luke.parent 一无所知。

    在你的代码之后

    luke.borrow_mut().parent = Some(anakin.clone());
    anakin.borrow_mut().child = Some(luke.clone());
    

    有 4 个 Rc 对象(2 个在堆栈上,2 个在堆上),以及 2 个在堆上的 RefCell 对象,它们具有相关的计数。如您所见,每个RefCell 的计数为2,因为有2 个Rcs 引用每个RefCell

    anakin 下降时,它的RefCell 计数减少,因此您在堆上的luke RefCell 计数为2anakin RefCell 计数1.

    luke 下降时,就会减少that RefCell 的计数,所以现在每个人都有1 的计数。您最终在堆栈上没有引用 RefCellRc 值,但每个 RefCell 引用 other RefCell,因此 Rust 无法知道它们是可以安全掉落。

    我无法从您的问题中完全看出,但这绝对是与 RefCell 一起使用时 Rc 类型的预期限制,因为它允许在对象所有权中引入循环。

    您的代码几乎是 Rc 循环的最小可重现示例:What is a minimal example of an Rc dependency cycle?

    【讨论】: