Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

Auto Release Pool in Pure C Language

3.80/5 (3 votes)
20 Jun 2009CPOL4 min read 28K   312  
Using pure C language to mimic the memory auto release pool in objective-C

Introduction

Recently, I have been learning how to write programs using Objective-C on Mac OS X. Objective-C 2.0 starts to offer garbage collection, but prior to that, memory management is performed explicitly by programmers using the so-called "auto release pools". While studying, I thought this kind of pool could also be applied in C programs, thus I would like to write one myself in C. Although I know there must be lots of such libraries around, I still would like to reinvent the wheel in pure C language.

Background

The mechanism of "auto release pools" isn't difficult: whenever you allocate a new block of memory, you add the reference of that block to the current auto release pool at the same time. Then, later at one point of execution, you release (free) the pool and so all the memory blocks referenced by that pool will be released (freed), too. In this way, you don't need to scatter your release/delete code all over your program.

The above description is just too simplified. Readers can search on the web for more information.

Implementation of this Auto Release Pool

In Objective-C, the allocated objects and auto release pool consist of the following characteristics:

  1. All allocated objects have a reference count. Only when the reference count becomes 0 will the object get freed. When you free an auto release pool, all objects inside that pool will have their reference count decremented by 1 (so if the object has a ref count of 2, it won't get freed after you free the pool, it's the responsibility of the programmer to free that object later).
  2. Programmers are allowed to new more than one auto release pool and the latest allocated pool becomes the current pool.

Based on the above characteristics, the auto release pools are implemented like this:

autoreleasepool_data_structure.png

From the above design, each thread can have its own sets of pools. The pointer to the first pool is saved at a thread local storage slot (the blue block). New pool (orange blocks) can be newed and chained at the end of a doubly-linked list (the pool at the end, i.e. the right most in the above diagram, is the "current" pool). Each pool will then have its own sets of referenced memory blocks (green blocks), which are also chained in a doubly-linked list.

In order to chain up those memory blocks, extra memory is allocated to hold some information whenever user creates a new a block of memory:

allocated_memory_pattern.png

The block in pale violet is the actual block requested by a function call (I will show that function later), while the green block and yellow blocks are extra blocks for book-keeping purposes.

The green block is exactly the same block as in the previous diagram, which is a structure as this:

C++
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;

I think you can understand what the fields are from the comment. For the "dtorFunc", it is a destructor function specified by caller during allocation (it can be NULL) and its prototype is:

C++
void (*dtorfunc_t)(void*)

As you guess, this callback function is called during object deallocation and the pointer to the original allocated block is passed in, which acts like the destructor in C++.

The yellow blocks are just safety zones to test whether user's program has written beyond their allocated block.

Usage of this Code

Inside the source zip package, there are 2 VC 2005 projects --

  • MemReleasePool: The static library which contains the implementation of this auto release pool
  • TestMemPool: A demo command line program which lists some typical usages of the library

The main functions in the library are:

C++
//! 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);

Typical usage is as follows:

C++
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;
}

For other usage examples, please read "TestMemPool".

Also, when using this memory pool, follow the 3 rules of the AutoReleasePool of Objective-C:

  1. If memory is allocated from "MRP_Calloc", maintain the memory yourself (i.e. call "MRP_Release" yourself). "MRP_CallocARel" will attach the memory block to the current auto release pool automatically.
  2. If a pointer is obtained from other functions, do nothing. However, if you want to get hold of an object after releasing the current pool, retain it and release it finally yourself.
  3. Balance the number of "Retain" and "Release".

Important Note for this Library

Memory allocated by "MRP_Calloc" or "MRP_CallocARel" from one thread should NOT be passed to another thread and released there, otherwise, undefined result may occur.

Points of Interest

Garbage collection is certainly the better and painless way to manage memory, but it is not that easy to write an efficient garbage collector. This kind of auto release pool is half way between the traditional malloc/free patterns and garbage collection.

History

  • 20th June, 2009: First implementation 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)