【问题标题】:How to avoid freeing objects that are stored in containers with the same reference count如何避免释放存储在具有相同引用计数的容器中的对象
【发布时间】:2019-04-05 09:41:08
【问题描述】:

我一直在研究用 c 编写的自定义编程语言的一些特性。目前我正在开发一个系统,该系统对语言中的对象进行引用计数,在 c 中表示为结构,其中包括引用计数。
还有一个功能可以释放所有当前分配的对象(比如在程序退出之前清理所有内存)。现在这正是问题所在。

我一直在考虑如何做到最好,但我遇到了一些问题。让我大致描述一下情况:

分配了2个新整数。两者的引用计数均为 1
分配了 1 个新列表,引用计数也为 1

现在两个整数都在列表中,这使它们的引用计数为 2
在这些操作之后,由于某种原因,两个整数都超出了范围,因此它们的引用计数下降到 1,因为它们仍在列表中。

现在我已经完成了这些对象,所以我运行该函数来删除所有跟踪的对象。但是,您可能已经注意到列表和列表中的对象都具有相同的引用计数 (1)。这意味着无法决定首先释放哪个对象。

如果我要释放列表之前的整数,列表将尝试减少之前释放的整数的引用计数,这将导致段错误。

如果列表在整数之前被释放,它会将整数的引用计数减为 0,这也会自动释放它们,并且不需要采取进一步的步骤来释放整数。他们不再被跟踪。

目前我有一个大部分时间都可以工作的系统,但不适用于我上面给出的示例,其中我根据对象的引用计数释放对象。最高计数最新。这显然只适用于整数的引用计数高于上面示例中可见的列表的情况,但并非总是如此。 (它只在整数没有超出范围的情况下才有效,因此它们仍然具有比列表更高的引用计数)

注意:我已经找到了一种我真的不喜欢的方法:向每个对象添加一个标志,表明它在一个容器中,因此不能被释放。我不喜欢这样,因为它为每个分配的对象增加了一些内存开销,并且当存在循环依赖时,不会释放任何对象。当然,循环检测器可以解决这个问题,但最好我只想通过引用计数来做到这一点。

让我举一个上述步骤的具体例子:


//this initializes and sets a garbage collector object. 
//Basically it's a datastructure which records every allocated object,
//and is able to free them all or in the future 
//run some cycle detection on all objects. 
//It has to be set before allocating objects 

garbagecollector *gc = init_garbagecollector();
set_garbagecollector(gc);

//initialize a tracked object fromthe c integer value 10
myobject * a = myinteger_from_cint(10); 
myobject * b = myinteger_from_cint(10);

myobject * somelist = mylist_init();
mylist_append(somelist,a);
mylist_append(somelist,b);

// Simulate the going out of scope of the integers.
// There are no functions yet so i can't actually do it but this
// is a situation which can happen and has happened a couple of times
DECREF(a);
DECREF(b);

//now the program is done. all objects have a refcount of 1

//delete the garbagecollector and with that all tracked objects
//there is no way to prevent the integers being freed before the list
delete_garbagecollector(gc);

当然应该发生的是 100% 的时间,列表在整数之前被释放。

什么是释放所有现有对象的更聪明的方法,这样存储在容器中的对象不会在它们所在的容器之前被释放?

【问题讨论】:

  • 读到这里我有点头疼,但我在想,也许你不能使用你拥有的那些构建块来构建你想要的东西。 DECREF 和其他函数,是你自己写的还是来自某个库?
  • 我自己写了一切,这里没有库。虽然也许你是对的,我想要的太多了。能够释放所有被跟踪的对象感觉是一个非常好的系统,但也许我在那里要求太多了。
  • 好的,那么好的是您可以按照自己的方式设计一切。不好的部分是你需要设计它们。 :-) 你要求我们在这里做的是为你的语言设计一个后端,甚至没有指定语言。 :-)
  • 我尝试这样做的原因是因为我知道 python 有这样一个系统,因为它是带有 Py_FinalizeEx() 和 Py_InitializeEx() 的 c api,其中 finalize 也会释放所有内存,如下所述:@ 987654321@ "理想情况下,这会释放 Python 解释器分配的所有内存。"
  • 当程序结束并且列表句柄的范围结束时,为什么它的引用计数不减少到0?

标签: c containers programming-languages refcounting


【解决方案1】:

这取决于你的意图:

还有一个特性可以释放所有当前分配的对象(比如在程序退出之前清理所有内存)。

如果目标是强制释放每个对象而不考虑其引用计数,那么我将有一个单独的代码块遍历对象图并释放每个对象而不触及其引用计数。引用计数本身也将被释放,因此更新它没有什么意义。

如果目标只是告诉系统“我们不再需要对象”,那么另一种选择是简单地遍历根并减少它们的引用计数。如果没有其他对它们的引用,它们将为零。然后,他们将在被释放之前减少他们引用的所有内容的引用计数。这反过来又渗透到对象图中。如果根是唯一在你调用它的点上保持引用的东西,它将有效地释放所有东西。

【讨论】:

  • 这实际上是一个聪明的主意!现在,这正是我想要的答案。非常感谢您的回复!我完全忽略了引用计数确实不再重要的事实。有趣的是,一旦你开始思考一种方式,看到其他一些选择会变得非常困难。再次,谢谢!
  • 确实,我们的目标就是按照你说的强制释放所有东西
  • 只是一个小更新,如果有人会再次制作这样的语言并需要这样的系统:我通过在这个 free_all_objects 时禁用 DECREF() 宏中的析构函数调用解决了这一切函数被调用,这使得过早释放是不可能的。再次感谢您的启发。
【解决方案2】:

somelist 的引用计数为零之前,您不应释放任何东西。

【讨论】:

  • 通常不是没有,但在程序结束时我只想释放所有仍然存在的对象。什么都不会依赖于它只需要释放所有东西而不导致段错误的对象。在正常执行中,这永远不会发生。
  • @jonathan 在程序内存的末尾是您最不关心的问题,因为您可以将其全部释放。其他资源,如文件,可能需要正确关闭。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-04-22
  • 1970-01-01
  • 2018-02-13
  • 2012-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多