Table of contents
Quick Start
// cl main.c
#include <stdio.h>
#include <stdlib.h>
#define GB (1024 * 1024 * 1024) // 1 gigabyte
/*
---------- Arena ----------
Hello, 0World!0000000000000
^ ^ ^
str str2 offset
*/
typedef struct {
unsigned char *data;
size_t length;
size_t offset;
} Arena;
Arena arena_alloc(size_t sz) {
unsigned char *memory = malloc(sz);
Arena arena = {
.data = memory,
.length = sz,
.offset = 0
};
return arena;
}
void *arena_push(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 = arena_alloc(GB);
char *str = arena_push(&arena, 8 * sizeof(char));
memcpy(str, "Hello, ", 7);
str[7] = '\0';
char *str2 = arena_push(&arena, 7 * sizeof(char));
memcpy(str2, "World!", 6);
str2[6] = '\0';
printf("%s%s", str, str2);
// output: Hello, World!
return 0;
}
Tutorial
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.
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 often 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.
arena_alloc
Add these lines:
Arena arena_alloc(size_t sz) {
unsigned char *memory = malloc(sz);
Arena arena = {
.data = memory,
.length = sz,
.offset = 0
};
return arena;
}
int main() {
Arena arena = arena_alloc(GB);
return 0;
}
Arena arena = arena_alloc(GB);
creates a new arena that can hold 1 gigabyte.
arena_push
arena_push
allocates sz
bytes from the Arena
and returns the address of the allocated location:
void *arena_push(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 sizesz
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 inp
. 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 = arena_alloc(GB);
// Allocate memory.
char *str = arena_push(&arena, 8 * sizeof(char));
// In C++, you must cast the return value explicitly:
// char *str = (char *)arena_push(&arena, 8 * sizeof(char));
// Store data.
memcpy(str, "Hello, ", 7);
str[7] = '\0';
// Allocate more memory.
char *str2 = arena_push(&arena, 7 * sizeof(char));
// Store more data.
memcpy(str2, "World!", 6);
str2[6] = '\0';
printf("%s%s", str, str2);
// output: Hello, World!
return 0;
}
The two strings are stored contiguously in the memory, like this:
---------- Arena ----------
Hello, 0World!0000000000000
^ ^ ^
str str2 offset
Closing remarks
This tutorial introduces a very basic allocator. It doesn't handle out-of-memory situations or memory reuse.
Why not simply use malloc()
every time you want to allocate memory? Because it's not free to allocate and release memory, and it can also become cumbersome to manage lifetimes.
In this example, the allocation process simply involves returning a pointer to the next available slot, and we are not manually freeing memory; the entire memory chunk is released at once when the program exits.
What about garbage-collected languages? One issue with those is that the collection process may occur at inconvenient times. This can be problematic in applications where predictable results are expected, such as in games. However, you can also use arena-type memory management strategies in those languages as well.
Leave a comment