patterncMinor
Reference Counting in C99
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
The following figure shows what happens in memory after a call to
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 (
The code uses two macros (
```
#include
#include
#include
#include "mem.h"
#include "object.h"
#define OBJECT_TO_RECORD(ptr) ((ObjectRecord ) (((char ) ptr) - sizeof (
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 ofmalloc ()
- Whenever an object is assigned to a variable (e.g.
v = object) the client should make a callretain(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 qMemory 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
This does the arithmetic directly in units of
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:
In C, We don't normally cast the result of
or:
(I don't like
There are other approaches (such as registering a failure handler), but I think it's best to copy the existing
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!
(Compiled without warnings with my usual compiler:
#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.