如何在C中实现引用计数?

时间:2012-05-15 20:56:10

标签: c memory-management concurrency garbage-collection

了解它here

我需要实现这样一个接口的变体,比如说我们有一个大的内存空间来管理应该有getmem(大小)和free(指向块的指针)函数,它们必须确保空闲(指向块的指针)当且仅当使用该块的所有进程都使用它时才能释放内存。

我正在考虑做的是将Collectable结构定义为块的指针,它的大小以及使用它的进程计数。然后,只要第一次使用Collectable结构实例的进程必须显式增加计数,并且只要进程free(),它就会递减计数。

这种方法的问题在于所有进程都必须响应该接口并使其显式工作:每当为实例分配可收集指针时,进程必须明确地包含该计数器,这不满足我,我想也许有为每个作业隐式地创建一个宏的方法吗?

我正在寻找解决这个问题的方法,所以其他的方法和想法会很棒......

编辑:上述方法不满足我,不仅因为它看起来不太好,而且主要是因为我不能假设正在运行的进程的代码会关心更新我的计数。我需要一种方法来确保完成它而不改变进程的代码...

4 个答案:

答案 0 :(得分:4)

引用计数的早期问题是通过将代码放入自定义malloc / free实现来计算初始引用相对容易,但确定初始收件人是否将该地址传递给其他人是相当困难的

由于C缺乏覆盖赋值运算符的能力(计算新引用),基本上你只剩下有限数量的选项。唯一可以覆盖赋值的是macrodef,因为它能够将赋值重写为内联引用计数值增量的内容。

所以你需要“扩展”一个看起来像

的宏
a = b;

if (b is a pointer) { // this might be optional, if lookupReference does this work
  struct ref_record* ref_r = lookupReference(b);
  if (ref_r) {
    ref_r->count++;
 } else {
    // error
  } 
}
a = b;

真正的技巧是编写一个可以识别赋值的宏,并干净地插入代码而不会引入其他不需要的副作用。由于macrodef不是一种完整的语言,因此您可能会遇到无法进行匹配的问题。

(看到钉子在哪里学习如何使用锤子的笑话在这里有一个有趣的平行,除了当你只有一把锤子时,你最好学会如何把一切都做成钉子。)

其他选项(可能更加理智,可能不是)是跟踪malloc分配的所有地址值,然后扫描程序的堆栈和堆以查找匹配的地址。如果你匹配,你可能找到了一个有效的指针,或者你可能找到了一个运气编码的字符串;但是,如果你不匹配,你当然可以释放地址;只要他们没有存储从原始地址计算的地址+偏移量。 (也许你可以使用macrodef来检测这样的偏移,并在同一个块的扫描中将偏移量添加为多个地址)

最后,如果没有构建一个引用系统,那么你就不会有一个万无一失的解决方案,在那里你传回引用(假装地址);隐藏真实地址。这种解决方案的缺点是您必须每次使用库接口来处理地址。这包括数组中的“next”元素等。不是非常类似于C,而是Java与其引用的相似之处。

答案 1 :(得分:2)

半严肃答案

#include "Python.h"

Python有一个很好的引用计数内存管理器。如果我必须在生产代码中实现这一点,而不是家庭作业,我会考虑在我的C程序中嵌入python对象系统,这样我的C程序也可以在python中编写脚本。如果您有兴趣,请参阅the Python C API documentation

答案 2 :(得分:2)

C语言中的这样一个系统需要程序员的一些纪律但是......

您需要考虑所有权。所有持有引用的东西都是所有者,必须跟踪它所包含的对象,例如通过列表。当一个引用保持事物被破坏时,它必须循​​环其引用对象列表并减少它们的引用计数器,如果为零依次销毁它们。

函数也是所有者,应该跟踪引用的对象,例如通过在函数开头设置一个列表并在返回时循环它。

因此,您需要确定应在哪些情况下传输对象或与新所有者共享对象,并将相应的情况包含在添加或删除拥有对象的宏/函数中,以拥有对象的引用对象列表(并相应地调整引用计数器) )。

最后,您需要通过检查不再可从堆栈上的对象/指针访问的对象来以某种方式处理循环引用。这可以通过一些标记和清除垃圾收集机制来完成。

答案 3 :(得分:0)

如果没有可覆盖的析构函数/构造函数,我认为你不能自动完成它。 您可以查看HDF5引用计数,但这些需要在C中显式调用:

http://www.hdfgroup.org/HDF5/doc/RM/RM_H5I.html