4.1 KiB
4.1 KiB
title = Arena
title = Arena
Arena
An arena is a bump allocator, under the hood it can use one of 3 strategies to allocate its data:
Virtual: allocates with virtual memory, meaning that it reserves the data upfront, but only allocates one page at a time (usually 64 Kb). This is the preferred way to use the arena as it can freely allocate gigabytes of memory for freeMalloc: allocates the memory upfront using mallocStatic: uses a buffer passed by the user instead of allocating
To help with allocating big chunks of data, arena.h defines the macros KB, MB, and GB. If you don't need them you can define ARENA_NO_SIZE_HELPERS
To create an arena use the macro arenaMake:
arena_t varena = arenaMake(ARENA_VIRTUAL, GB(1));
uint8 buffer[1024];
arena_t sarena = arenaMake(ARENA_STATIC, sizeof(buffer), buffer);
To allocate use the alloc macro. The parameters to allocate are:
- A pointer to the arena
- The type to allocate
- (optional) the number of items to allocate
- (optional) flags:
ALLOC_NOZERO: by default the arena sets the memory to zero before returning, use this if you want to skip itALLOC_SOFT_FAIL: by default the arena panics when an allocation fails, meaning that it never returnsNULL.
- (automatic) the align of the type
- (automatic) the size of the type
Example usage:
// allocate 15 strings
str_t *string_list = alloc(arena, str_t, 15);
// allocate a 1024 bytes buffer
uint8 *buffer = alloc(arena, uint8, 1024);
// allocate a structure
game_t *game = alloc(arena, game_t);
The strength of the arena is that it makes it much easier to reason about lifetimes, for instance:
// pass a pointer to the arena for the data that we need to return
WCHAR *win32_get_full_path(arena_t *arena, const char *rel_path) {
// we need a small scratch arena for allocations that
// will be thrown away at the end of this function
uint8 scratch_buf[1024];
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(scratch_buf, scratch_buf));
WCHAR *win32_rel_path = str_to_wchar(&scratch, rel_path);
DWORD pathlen = GetFullPathName(win32_rel_path, 0, NULL, NULL);
WCHAR *fullpath = alloc(arena, WCHAR, pathlen + 1);
GetFullPath(win32_rel_path, pathlen + 1, fullpath, NULL);
// notice how we don't need to free anything at the end
return fullpath;
}
There are a few helper functions:
arenaScratch: sub allocate an arena from another arenaarenaTell: returns the number of bytes allocatedarenaRemaining: returns the number of bytes that can still be allocatedarenaRewind: rewinds the arena to N bytes from the start (effectively "freeing" that memory)arenaPop: pops N bytes from the arena (effectively "freeing" that memory)
Here is an example usage of a full program:
typedef struct {
char *buf;
usize len;
} str_t;
str_t read_file(arena_t *arena, const char *filename) {
str_t out = {0};
FILE *fp = fopen(filename, "rb");
if (!fp) goto error;
fseek(fp, 0, SEEK_END);
out.len = ftell(fp);
fseek(fp, 0, SEEK_SET);
out.buf = alloc(arena, char, out.len + 1);
fread(out.buf, 1, out.len, fp);
error:
return out;
}
void write_file(const char *filename, str_t data) {
FILE *fp = fopen(filename, "wb");
if (!fp) return;
fwrite(data.buf, 1, data.len, fp);
}
int main() {
arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1));
str_t title = {0};
{
uint8 tmpbuf[KB(5)];
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
str_t file = read_file(&scratch, "file.json");
json_t json = json_parse(&scratch, file);
title = str_dup(arena, json_get(json, "title"));
}
{
// copying an arena by value effectively makes it a scratch arena,
// as long as you don't use the original inside the same scope!
arena_t scratch = arena;
str_t to_write = str_fmt(
&scratch,
"{ \"title\": \"%s\" }", title.buf
);
write_file("out.json", to_write);
}
// cleanup all allocations at once
arenaCleanup(&arena);
}