I like implementing low-level things in C. It sort of helps me visualize how things work at a low memory level.
Consider the following example:
int *p = malloc(3); This pointer now holds the first address of a contiguous memory block of three bytes.

Now this memory is allocated on the heap. It has to be freed eventually, else we’ll end up with a memory leak.
The Problem and the Solution
Keeping track of multiple pointers that need to be freed is hard.
Memory arenas provide a simple solution to this problem.
- Declare a contiguous memory block (arena) that is big enough to hold all the data you’ll ever need.
- Use and reuse that memory block.
- If some data that has been allocated goes out of scope, make sure to dispose of it to regain memory available for use.
- Once everything is done, free that memory block.
This approach makes it possible for us to avoid having to call free multiple times. We only call it once when we are done with the program and no longer need to create pointers.
The Implementation
We need 5 functions.
| Function | Return Type | Parameters | Description |
|---|---|---|---|
| ArenaInitialize | (Arena *) | (size_t size) | To initialize/allocate the memory arena |
| ArenaAllocate | (char *) | (Arena *arena, size_t size, datatype value) | To allocate pointers on the memory arena |
| ArenaPrinter | void | (Arena *arena, size_t size) | To print the contents of the memory arena |
| ArenaReset | void | (Arena *arena) | To clear the memory arena (clear the pointers that we have previously allocated) |
| ArenaFree | void | (Arena *arena) | To deallocate the memory arena |
Now let’s step through some code!
typedef struct {
size_t size;
size_t currentOffset;
char *buffer;
} Arena;First, we have a struct that is used to initialize the Arena.
- The
sizemember is used to store the size of the initialized struct (in bytes). - The
currentOffsetis used to keep track of how much data has been written into the Arena. For example, if the Arena is storing 9 chars, then thecurrentOffsetis 9 bytes (since every char is 1 byte). bufferstores the actual data. Note that it’s achar *becausecharis 1 byte and that it is yet to be initialized.
Arena *ArenaInit(size_t size) {
// if size == 0 then return NULL
if (!size) {
return NULL;
}
// allocate the arena pointer
Arena *arenaPtr = malloc(sizeof(Arena));
// allocate the buffer to 1 byte (char) * number of elements (size)
arenaPtr->buffer = calloc(size, sizeof(char));
arenaPtr->size = size;
arenaPtr->currentOffset = 0;
return arenaPtr;
}- While initializing the arena, we have to allocate the arena and then the buffer inside it. I’ve used
mallocandcallochere, but it can be done with either of them. - The arena’s size is set to the
sizeargument that is passed by the user. It’s multiplied withcharbecausecharis usually 1 byte in size. It’s also recorded in a variable. - The
currentOffsetis set to 0. This is done to keep track of where we are when allocating space on the arena. - Finally, the function returns a pointer to the initialized memory arena.
char *ArenaAlloc(Arena *arena, size_t size, int value) {
// if the arena offset is greater than or equal to the arena size then return
// NULL
if ((arena->currentOffset + size) > arena->size) {
return NULL;
}
// get the current offset
char *currentOffset = arena->buffer + arena->currentOffset;
// set the value and increment the offset
memcpy(currentOffset, &value, size);
arena->currentOffset += size;
// return the current offset
return currentOffset;
}After we are done initializing and allocating memory to the arena, we can now allocate space on it.
- First, we check if the current offset will exceed the arena’s size when more data is added to it. If it does, then we return
NULL, in which case allocating memory on the arena has failed. - Then we get the current offset pointer. This is done using pointer arithmetic, where we add the integer size of the current offset to
arena->buffer. Notice thatarena->bufferis a pointer itself, and when referenced, it decays to a pointer to the first element. - Now we copy data after the current offset with
memcpy. - We update the current offset by adding the size of the data copied to the arena.
- Finally, we return the current offset. Notice that this is the offset that shows the beginning of the allocated data and not the current offset of the memory arena.
void ArenaPrinter(Arena *arena, size_t size) {
size_t currentOffset = arena->currentOffset;
size_t counter = 0;
if (arena->currentOffset == 0) {
fprintf(stderr, "Error: Arena is empty");
exit(1);
}
while (counter != currentOffset) {
printf("%d\n", *(arena->buffer + counter));
counter += size;
}
}- This is a function that prints the contents of the memory arena. It’s purely for debugging purposes.
void ArenaReset(Arena *arena) {
// clear the buffer
memset(arena->buffer, 0, arena->size);
// reset the current
arena->currentOffset = 0;
}- Say we want to remove the allocated data. Normally we would do this with
free, but since we are using an arena, we can just overwrite the whole arena with0(or not, but this seems cleaner, likecalloc) and reset the current offset of the arena to 0.
void ArenaFree(Arena *arena) {
// first free the buffer in arena
free(arena->buffer);
// then free the arena itself
free(arena);
}- While freeing the arena, it’s very important that we first free the buffer inside the arena and then the arena itself. If we free the arena first, the pointer to the buffer will be lost and this will result in a memory leak.
Conclusion
And that’s it. A clean, simple and memory-safe solution for allocating and deallocating pointers in pure C :)