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:
- 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).
- 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:
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:
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:
typedef struct _memnode
{
dllist_t dlist; memrelpool_t * pRelPool; dtorfunc_t dtorFunc; int refCount; sz_t size; } 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:
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:
memrelpool_t * MRP_CreateRelPool();
void MRP_FreePool(memrelpool_t * pRelPool);
int MRP_GetPoolNodesCount(memrelpool_t * pRelPool);
int MRP_GetPoolsCount();
int MRP_Release(void * pMemLoc);
int MRP_Retain(void * pMemLoc);
void * MRP_Calloc(sz_t num, sz_t size);
void * MRP_CallocDtor(sz_t num, sz_t size, dtorfunc_t dtorFunc);
void * MRP_CallocARel(sz_t num, sz_t size);
void * MRP_CallocARelDtor(sz_t num, sz_t size, dtorfunc_t dtorFunc);
void * MRP_Realloc(void * pMemLoc, sz_t size);
Typical usage is as follows:
int main(void)
{
memrelpool_t * pRelPool = MRP_CreateRelPool();
int * arrA = (int*)MRP_CallocARel(4, sizeof(int));
int * arrB = (int*)MRP_CallocARel(3, sizeof(int));
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:
- 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. - 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.
- 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