Making Money with Django and AI: How to Build SaaS Services Using Python

Learn how to build AI-driven websites with the Django web framework and monetize your services.
Read more
Memory Arena Tutorial in C
How to create a simple arena allocator in C.
Updated Dec 21, 2023

Introduction

One straightforward way to handle memory is to allocate a large contiguous chunk of memory when the program starts and then use that chunk throughout the program's lifetime. When the program exits, the memory is released.

Why not simply use malloc()? Because it's not free to allocate and release memory, and it can also become cumbersome to manage lifetimes. You have to remember to call free() for each allocation.

This tutorial introduces a very basic arena allocator. It doesn't handle out-of-memory situations and memory reuse, but it demonstrates the point. You can extend the implementation as your needs grow. For example, you can have one arena type that lasts the entire lifetime of the project and another that lasts just one frame. You can add "pop" and "clear" functions, use linked lists to add new chunks of memory, and reuse spots using a "free list," etc.

Why not use modern, garbage-collected languages? Well, you can, but for starters, it can be problematic in applications where predictable results are expected, as the collection process may occur at inconvenient times.

You can also use arena-type memory management strategies in garbage-collected languages. For example, there seems to be an experimental arena implementation in Go.

Arena struct

Create a file named main.c and add these lines to it:

#include <stdio.h>
#include <stdlib.h>

#define GB (1024 * 1024 * 1024)

typedef struct {
    unsigned char *data;
    size_t length;
    size_t offset;
} Arena;
  • unsigned char is typically used as buffer type for raw memory manipulation.
  • size_t is an unsigned integer type guaranteed to be large enough to contain the size of the largest possible object on the system. It is typically used in memory allocation and array indexing.
  • length represents the total size of the available memory in the arena, measured in number of bytes.
  • offset is used to obtain the next available memory slot in the arena, measured in number of bytes.

ArenaAlloc()

Add these lines:

Arena ArenaAlloc(size_t sz) {
    unsigned char *memory = malloc(sz);
    Arena arena = {
        .data = memory,
        .length = sz,
        .offset = 0
    };
    return arena;
}

int main() { 
    Arena arena = ArenaAlloc(GB);
    return 0;
}

Arena arena = ArenaAlloc(GB); creates a new arena that can hold 1 gigabyte.

ArenaPush()

ArenaPush allocates sz bytes from the Arena and returns the address of the allocated location:

void *ArenaPush(Arena *a, size_t sz) {
    if(a->offset+sz <= a->length) {
        void *p = &a->data[a->offset];
        a->offset += sz;
        memset(p, 0, sz);
        return p;
    }   
    printf("Game over, man. Game over!"); // Handle out of memory.
    return NULL;
}   
  • if(a->offset+sz <= a->length) { checks whether the requested allocation size sz fits in the arena. This simple allocator doesn't handle memory resizing.
  • void *p = &a->data[a->offset] obtains an address to the next available slot and stores it in p. Initially, the offset is zero, so it points to the beginning of the arena.
  • a->offset += sz advances the offset, making it to point to the next available memory slot.
  • memset(p, 0, sz) zero-initializes the memory.
  • return p returns the address to the memory we just allocated.
  • If the requested size doesn't fit in the arena, we simply print out a message and return NULL. You should handle this scenario better.

Usage

Here is how you can use the Arena:

int main() {
  
    // Create arena.
    Arena arena = ArenaAlloc(GB); 

    // Allocate memory.
    char *str = ArenaPush(&arena, 8 * sizeof(char)); 
    
    // In C++, you must cast the return value explicitly:
    // char *str = (char *)ArenaPush(&arena, 8 * sizeof(char));
    
    // Store data.
    memcpy(str, "Hello, ", 7); 
    str[7] = '\0';
    
    // Allocate more memory.
    char *str2 = ArenaPush(&arena, 7 * sizeof(char));

    // Store more data.
    memcpy(str2, "World!", 6);
    str2[6] = '\0';
    
    printf("%s%s", str, str2);
    // output: Hello, World!

    return 0;
}

Memory contents

The two strings are now stored contiguously in the memory, like this:

---------- Arena ----------
Hello, 0World!0000000000000
^       ^      ^
str     str2   offset

Final Code

// cl main.c /Zi /nologo

#include <stdio.h>
#include <stdlib.h>

#define GB (1024 * 1024 * 1024)

typedef struct {
    unsigned char *data;
    size_t length;
    size_t offset;
} Arena;

Arena ArenaAlloc(size_t sz) {
    unsigned char *memory = malloc(sz);
    Arena arena = {
        .data = memory,
        .length = sz,
        .offset = 0
    };
    return arena;
}

void *ArenaPush(Arena *a, size_t sz) {
    if(a->offset+sz <= a->length) {
        void *p = &a->data[a->offset];
        a->offset += sz;
        memset(p, 0, sz);
        return p;
    }   
    printf("Game over, man. Game over!"); // Handle out of memory.
    return NULL;
}   


int main() {
  
    Arena arena = ArenaAlloc(GB);

    char *str = ArenaPush(&arena, 8 * sizeof(char));
    memcpy(str, "Hello, ", 7);
    str[7] = '\0';
    
    char *str2 = ArenaPush(&arena, 7 * sizeof(char));
    memcpy(str2, "World!", 6);
    str2[6] = '\0';
    
    printf("%s%s", str, str2);

    // output: Hello, World!

    return 0;
}

Video