Reference counting

C's requirements of manual memory management presents the problem of deciding exactly when it is safe to free an object. Do it too soon, and you end up with invalid pointers which will lead to crashes or data corruption later in execution. Forget to do it, and you have a memory leak, which in a long-running process can result in all memory being exhausted.

This difficulty primarily arises in situations where there are multiple references to a given object from different places. If you create an object, do something with it, and then no longer need it, then freeing it at that point is an obvious choice. But if you create an object and pass it to functions which may store a reference to it, or if you return it to the caller, then there's disconnect between the creation point and the destruction point, with it often being unclear where the latter should actually be.

In Corinthia, we use a technique known as reference counting to deal with this. Each object has a reference count associated with it, which records the number of "places" from where it is referenced. When a new reference is established, the reference count must be incremented. When a reference is removed or the source of the reference is destroyed, the reference count must be decremented. The way in which we use this mechanism is derived from Apple's conventions for the Cocoa and Core Foundation frameworks in OS X and iOS, although its implementation is entirely separate and does not rely on these technologies.

One important caveat of reference counting relative to garbage collection is the risk of cycles — if object A references object B, and object B references object A, then if at some point during execution there are no longer any references to either from anywhere in the program, they will still have a non-zero reference count and thus remain in memory indefinitely. As a programmer, you must be aware of the structure of the reference graph to and ensure that cycles are either avoided entirely, or "torn down" at the appropriate time by breaking the cycle.

Reference-counting in Corinthia is managed accoridng to the following conventions:

  1. When an object is initially created, it has a reference count of 1
  2. When a new reference to an object is established, the object's Retain method is called, which increments the reference count.
  3. When an existing reference to an object is changed or cleared, the object's Release method is called, which decrements the reference count. If this causes the reference count to become zero, the object is destroyed, freeing it from memory.

Each "class" (that is, a C struct and a set of related functions) in Corinthia that uses reference counting implements this mechanism itself, so that it can remain entirely independent of other code, at least in this respect. The class provides one or more constructor methods (typically named New), as well as a Retain and Release method. The Retain method simply increments the reference count and returns a pointer to the object, and the Release method decrements the pointer and frees the object if necessary. If Retain is passed a NULL pointer, it simply returns NULL. If Release is passed a NULL pointer, it does nothing.

Implementing reference counting

A typical implementation of this for a class Foo would look like the following.

First, we use a typedef so that the class can be referred to simply by its name, instead of having to use the "struct" keyword:

typedef struct Foo Foo;

Next, we define the struct itself. The only field required for reference countaing is retainCount. By convention, we use size_t (an unsigned integral type), however in principle any integral type could be used here. The name "retainCount" (as opposed to "referenceCount") comes from that used in Objective C, in which the project was originally being developed.

struct Foo {
size_t retainCount;
// ...
};

The constructor function allocates memory for the object and initialises the reference count to 1.

Foo *FooNew(void)
{
Foo *f = (Foo *)xcalloc(1,sizeof(Foo));
f->retainCount = 1;
return f;
}

The Retain function increments the reference count, but only if the supplied pointer is non-NULL. This allows the caller to safely call the function on any field or variable without first having to check if it is NULL — this is done in many places when initialising fields of an object where it is not known whether the supplied parameter is NULL or not.

Foo *FooRetain(Foo *f)
{
if (f != NULL)
f->retainCount++;
return f;
}

The release function decrements the reference count and destroys the object. Here, we don't have any other fields, so a single call to free is sufficient. Typically, there would be calls to the Release methods of other reference-counted objects and/or other calls to free for fields the object. Like Retain, this function is also safe to use with NULL pointers, so callers can use it without first checking the value.

void FooRelease(Foo *f)
{
if ((f == NULL) || (--f->retainCount > 0))
return;
    free(f);
}

Wrapper Functions

We use wrapper functions for malloc(), calloc() and realloc(), which are called xmalloc(), xcalloc() and xrealloc() respectively.
These functions contain error checking and reporting routines and are located in DocFormat/platform/src/Wrapper.c.
  • No labels