65.9K
CodeProject 正在变化。 阅读更多。
Home

纯 C 语言的自动释放池

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.80/5 (3投票s)

2009年6月20日

CPOL

4分钟阅读

viewsIcon

28322

downloadIcon

327

使用纯 C 语言模拟 Objective-C 中的内存自动释放池

引言

最近,我一直在学习如何在 Mac OS X 上使用 Objective-C 编写程序。Objective-C 2.0 开始提供垃圾回收,但在那之前,内存管理由程序员使用所谓的“自动释放池”显式执行。在学习过程中,我想到这种池也可以应用于 C 程序,因此我想用 C 语言自己编写一个。虽然我知道肯定有很多这样的库,但我仍然想用纯 C 语言重新发明这个轮子。

背景

“自动释放池”的机制并不难:每当您分配一个新的内存块时,您同时将该块的引用添加到当前的自动释放池中。然后,在执行的某个时间点,您释放(free)该池,这样该池引用的所有内存块也将被释放(free)。通过这种方式,您无需将您的释放/删除代码分散到程序的各个地方。

以上描述过于简化。读者可以在网络上搜索更多信息。

此自动释放池的实现

在 Objective-C 中,已分配的对象和自动释放池具有以下特征

  1. 所有已分配的对象都有一个引用计数。只有当引用计数变为 0 时,该对象才会被释放。当您释放一个自动释放池时,该池内的所有对象的引用计数将减少 1(因此,如果该对象的引用计数为 2,则在您释放池后它不会被释放,这是程序员稍后释放该对象的责任)。
  2. 允许程序员新建多个自动释放池,并且最新分配的池成为当前池。

基于上述特征,自动释放池的实现如下

autoreleasepool_data_structure.png

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

为了链接那些内存块,当用户创建一个新的内存块时,会分配额外的内存来保存一些信息

allocated_memory_pattern.png

浅紫色的块是函数调用请求的实际块(稍后我将展示该函数),而绿色块和黄色块是用于簿记目的的额外块。

绿色块与上图中的块完全相同,它是一个结构体,如下所示

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 个规则

  1. 如果内存是从“MRP_Calloc”分配的,则自行维护内存(即,自行调用“MRP_Release”)。“MRP_CallocARel”将自动将内存块附加到当前的自动释放池。
  2. 如果指针是从其他函数获得的,请勿执行任何操作。但是,如果您想在释放当前池后拥有一个对象,请保留它,并最终自行释放它。
  3. 平衡“Retain”和“Release”的数量。

此库的重要注意事项

从一个线程的“MRP_Calloc”或“MRP_CallocARel”分配的内存不应传递给另一个线程并在那里释放,否则,可能会出现未定义的结果。

关注点

垃圾回收无疑是管理内存的更好、更轻松的方式,但编写一个高效的垃圾回收器并非易事。这种自动释放池介于传统的 malloc/free 模式和垃圾回收之间。

历史

  • 2009 年 6 月 20 日:首次实现
© . All rights reserved.