纯 C 语言的自动释放池
使用纯 C 语言模拟 Objective-C 中的内存自动释放池
引言
最近,我一直在学习如何在 Mac OS X 上使用 Objective-C 编写程序。Objective-C 2.0 开始提供垃圾回收,但在那之前,内存管理由程序员使用所谓的“自动释放池”显式执行。在学习过程中,我想到这种池也可以应用于 C 程序,因此我想用 C 语言自己编写一个。虽然我知道肯定有很多这样的库,但我仍然想用纯 C 语言重新发明这个轮子。
背景
“自动释放池”的机制并不难:每当您分配一个新的内存块时,您同时将该块的引用添加到当前的自动释放池中。然后,在执行的某个时间点,您释放(free)该池,这样该池引用的所有内存块也将被释放(free)。通过这种方式,您无需将您的释放/删除代码分散到程序的各个地方。
以上描述过于简化。读者可以在网络上搜索更多信息。
此自动释放池的实现
在 Objective-C 中,已分配的对象和自动释放池具有以下特征
- 所有已分配的对象都有一个引用计数。只有当引用计数变为 0 时,该对象才会被释放。当您释放一个自动释放池时,该池内的所有对象的引用计数将减少 1(因此,如果该对象的引用计数为 2,则在您释放池后它不会被释放,这是程序员稍后释放该对象的责任)。
- 允许程序员新建多个自动释放池,并且最新分配的池成为当前池。
基于上述特征,自动释放池的实现如下

从以上设计来看,每个线程都可以拥有自己的池集合。指向第一个池的指针保存在线程局部存储槽中(蓝色块)。新的池(橙色块)可以新建并链接到双链表的末尾(末尾的池,即上图中右侧的池,是“当前”池)。每个池都将拥有自己引用的内存块集合(绿色块),这些块也以双链表的形式链接在一起。
为了链接那些内存块,当用户创建一个新的内存块时,会分配额外的内存来保存一些信息

浅紫色的块是函数调用请求的实际块(稍后我将展示该函数),而绿色块和黄色块是用于簿记目的的额外块。
绿色块与上图中的块完全相同,它是一个结构体,如下所示
typedef struct _memnode
{
dllist_t dlist; // doubly linked list
memrelpool_t * pRelPool; // the pool it belongs to
dtorfunc_t dtorFunc; // destructor function if any
int refCount; // ref count
sz_t size; // size allocated
} memnode_t;
我想你可以从注释中理解这些字段的含义。对于“dtorFunc
”,它是由调用者在分配期间指定的析构函数(它可以是 NULL
),其原型是
void (*dtorfunc_t)(void*)
正如您所猜测的,此回调函数在对象释放期间被调用,并且传递了指向原始分配块的指针,这就像 C++ 中的析构函数一样。
黄色块只是安全区域,用于测试用户的程序是否已写入其分配的块之外。
此代码的用法
在源代码 zip 压缩包中,有两个 VC 2005 项目 --
MemReleasePool
:包含此自动释放池实现的static
库TestMemPool
:一个演示命令行程序,列出了该库的一些典型用法
该库中的主要函数是
//! create a new pool
memrelpool_t * MRP_CreateRelPool();
//! release the pool and all its block reference
void MRP_FreePool(memrelpool_t * pRelPool);
//! get number of memory blocks referenced by this pool
int MRP_GetPoolNodesCount(memrelpool_t * pRelPool);
//! get number of pools
int MRP_GetPoolsCount();
//! increment reference count by 1
int MRP_Release(void * pMemLoc);
//! decrement reference count by 1
int MRP_Retain(void * pMemLoc);
//! allocate memory just like 'calloc' WITHOUT adding to current pool
void * MRP_Calloc(sz_t num, sz_t size);
//! just like above with a destructor function
void * MRP_CallocDtor(sz_t num, sz_t size, dtorfunc_t dtorFunc);
//! allocate memory and add itself to the current pool
void * MRP_CallocARel(sz_t num, sz_t size);
//! just like above with a destructor function
void * MRP_CallocARelDtor(sz_t num, sz_t size, dtorfunc_t dtorFunc);
//! reallocate memory
void * MRP_Realloc(void * pMemLoc, sz_t size);
典型用法如下
int main(void)
{
// allocate a new pool
memrelpool_t * pRelPool = MRP_CreateRelPool();
// allocate memory which will be attached to the current pool automatically
int * arrA = (int*)MRP_CallocARel(4, sizeof(int));
int * arrB = (int*)MRP_CallocARel(3, sizeof(int));
// do some useful things here
// release the pool will also release memory blocks attached to it
MRP_FreePool(pRelPool);
return 0;
}
有关其他用法示例,请阅读“TestMemPool
”。
此外,在使用此内存池时,请遵循 Objective-C 的 AutoReleasePool
的 3 个规则
- 如果内存是从“
MRP_Calloc
”分配的,则自行维护内存(即,自行调用“MRP_Release
”)。“MRP_CallocARel
”将自动将内存块附加到当前的自动释放池。 - 如果指针是从其他函数获得的,请勿执行任何操作。但是,如果您想在释放当前池后拥有一个对象,请保留它,并最终自行释放它。
- 平衡“
Retain
”和“Release
”的数量。
此库的重要注意事项
从一个线程的“MRP_Calloc
”或“MRP_CallocARel
”分配的内存不应传递给另一个线程并在那里释放,否则,可能会出现未定义的结果。
关注点
垃圾回收无疑是管理内存的更好、更轻松的方式,但编写一个高效的垃圾回收器并非易事。这种自动释放池介于传统的 malloc/free 模式和垃圾回收之间。
历史
- 2009 年 6 月 20 日:首次实现