HiveBrain v1.2.0
Get Started
← Back to all entries
patterncMinor

Reference Counting in C99

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
referencecountingc99

Problem

(I created a GitHub repository; there are few improvements as suggested by other users in the comments)

As we know, C99 has no garbage collector. To address the problem of manually deallocate objects I wrote a little framework for reference counting. I would like to have comments about the style and clarity of the code and about the idea itself.

The main idea is to supersede malloc () function with a new function named alloc (). This function allocate the bytes needed for an object plus some meta information. The client of the library should follow this pattern:

  • Allocate objects with alloc () function instead of malloc ()



  • Whenever an object is assigned to a variable (e.g. v = object) the client should make a call retain(object)



  • Whenever a reference to an object is lost the client should call release(object)



The following figure shows what happens in memory after a call to alloc ():

+---+---+---------+
| 1 | 2 |    3    |
+---+---+---------+
  ^       ^
  p       q


Memory area noted with (1) is an integer, counting references to the object. Memory area noted with (2) is a pointer to the destructor function (typedef void (Destructor) (void object);); whenever the reference count reaches 0 this function is called. (1) and (2) form what the code calls ObjectRecord. Memory area noted with (3) represents the space needed by the actual object. The figure also shows two pointers, namely p and q. p is the pointer to the ObjectRecord and q is the pointer to the object. alloc () function returns q (the pointer to the actual object) so the client is not bothered by metadata.

The code uses two macros (OBJECT_TO_RECORD and RECORD_TO_OBJECT) to "translate" from p to q. mem.h library contains function malloc_or_die () that simply terminate the process upon failure.

```
#include
#include
#include

#include "mem.h"
#include "object.h"

#define OBJECT_TO_RECORD(ptr) ((ObjectRecord ) (((char ) ptr) - sizeof (

Solution

A simpler way to write those macros would be

#define OBJECT_TO_RECORD(object) ((ObjectRecord*)(object) - 1)
#define RECORD_TO_OBJECT(record) ((void*)(record + 1))


This does the arithmetic directly in units of ObjectRecord, so you don't need the sizeof operator.

Functions are better than macros, and there's no reason that the objectrecord conversions need to be macros. We should trust the compiler to inline these:

static ObjectRecord *toRecord(void *object) { return (ObjectRecord*)(object) - 1; }
static void *fromRecord(ObjectRecord *record) { return record + 1; }


In C, We don't normally cast the result of malloc(), so you should write:

ObjectRecord *record = malloc_or_die (sizeof (ObjectRecord) + size);


or:

ObjectRecord *record = malloc(size + sizeof *record);


(I don't like malloc_or_die() - guessing from the name, it sounds user-hostile. Terminating the entire application is definitely overkill for a fairly normal failure case, and not something that a library would want to be doing. And this is the kind of code that is otherwise useful for libraries, I think.)

There are other approaches (such as registering a failure handler), but I think it's best to copy the existing malloc() interface, and return a null pointer on failure. Every competent C programmer knows how to handle that.

My version

This has some additional changes, some of which are subjective preferences. As a C++ programmer, I obviously prefer prefix increment/decrement operators. I also find early exit preferable to conditionals, and don't like explicit comparisons against NULL. Your opinions may differ!

#include 
#include 
#include 

/* Internals */

typedef void (*Destructor)(void*);

typedef struct {
    unsigned int count;
    Destructor destroy;
} ObjectRecord;

static ObjectRecord *objectToRecord(void *p)
{
    return (ObjectRecord*)p - 1;
}
static void *recordToObject(ObjectRecord *r)
{
    return r + 1;
}

/* Public implementation */

void *alloc(size_t size, Destructor destroy)
{
    ObjectRecord *record = malloc(size + sizeof *record);
    if (!record)
        return record;

    record->count = 0;
    record->destroy = destroy;
    return recordToObject(record);
}

unsigned int refsof(void *p)
{
    return objectToRecord(p)->count;
}

void release(void *p)
{
    ObjectRecord *record = objectToRecord(p);
    assert(record->count > 0);

    if (--record->count)
        /* still referenced */
        return;

    if (record->destroy)
        record->destroy(p);
    free(record);
}

void retain(void *p)
{
    ++objectToRecord(p)->count;
}


(Compiled without warnings with my usual compiler: gcc -std=c11 -fPIC -g -Wall -Wextra -Wwrite-strings -Wno-parentheses).

Code Snippets

#define OBJECT_TO_RECORD(object) ((ObjectRecord*)(object) - 1)
#define RECORD_TO_OBJECT(record) ((void*)(record + 1))
static ObjectRecord *toRecord(void *object) { return (ObjectRecord*)(object) - 1; }
static void *fromRecord(ObjectRecord *record) { return record + 1; }
ObjectRecord *record = malloc_or_die (sizeof (ObjectRecord) + size);
ObjectRecord *record = malloc(size + sizeof *record);
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

/* Internals */

typedef void (*Destructor)(void*);

typedef struct {
    unsigned int count;
    Destructor destroy;
} ObjectRecord;


static ObjectRecord *objectToRecord(void *p)
{
    return (ObjectRecord*)p - 1;
}
static void *recordToObject(ObjectRecord *r)
{
    return r + 1;
}

/* Public implementation */

void *alloc(size_t size, Destructor destroy)
{
    ObjectRecord *record = malloc(size + sizeof *record);
    if (!record)
        return record;

    record->count = 0;
    record->destroy = destroy;
    return recordToObject(record);
}

unsigned int refsof(void *p)
{
    return objectToRecord(p)->count;
}

void release(void *p)
{
    ObjectRecord *record = objectToRecord(p);
    assert(record->count > 0);

    if (--record->count)
        /* still referenced */
        return;

    if (record->destroy)
        record->destroy(p);
    free(record);
}

void retain(void *p)
{
    ++objectToRecord(p)->count;
}

Context

StackExchange Code Review Q#146561, answer score: 3

Revisions (0)

No revisions yet.