philipbohun.com

gitlab | github | youtube | twitter

blog dark mode

Structured Allocation: Safe Memory Management In C

2025-09-13

There is a very simple pattern of memory allocation using arenas that makes memory management in C safe, easy, and efficient. I've been using this pattern for a little while now, and it has dramatically simplified working with memory in C for me.

Memory Management Problems

Any language that has manual memory management has a number of problems that must be solved including:

Libraries in C solve the ownership problem by either: In general, solving the ownership problem, as well as the others, usually involves some version of "be careful". This may include using a number of tools and analyzers to find mismanagement of memory, etc.

Aside: Arenas

If you already are familiar with arenas, you can skip this section. Otherwise, I'll give you a brief introduction to what arenas are. First, I should make the caveat that people have different names for what I'm calling an "arena". Some people call it a "bump allocator", some call it a "region allocator". There are probably other names. I'm just going to use the term "arena".

An arena allocator is a pool of memory that acts similar to a stack. Allocating memory involves returning a pointer to a location in the pool and bumping that pointer by the number of bytes allocated. Typically, arenas cannot free individual allocations. Instead, an arena is "reset" by just returning the memory pointer back to its original position. This "frees" all allocations that were made to that allocator all at once.

Structured Allocation

Recently, I've been using a strategy for memory allocation that I'm calling Structured Allocation. I like to think of it as an analogy to "structured programming". Structured programming introduced the concept of breaking code into managemable blocks that are delineated by control flow such as loops, if-then-else statements, switch statements, etc. By introducing restrictions to how people wrote code, it became more readable, modular, modifiable, debuggable, etc. The concepts behind structured programming were so successful that every major programming language has included them since the 1960s.

Structured allocation is similar. The idea is that instead of using malloc and free anywhere in our program, we actually restrict our allocations to grouped lifetimes collected into arenas. Typically, the lifetime of these arenas is the lifetime of a function call. There are only 3 simple rules to structured allocation:

  1. All allocations that would normally be stack-based should remain that way (e.g. "int x = 22;").
  2. All temporary dynamic allocations local to a block/scope should be done with a local arena.
  3. Any allocation that will outlast the lifetime of a block/scope must be allocated from an arena passed into that block/scope.
That's it! Let's look at an example.

Example

The following is a very simplified and contrived example, but should be enought to illustrate the point.


#include "clib.h"

typedef struct Person {
	char *name;
	int   favorite_int;
} Person;

Person get_person(Arena *a);

int main(void) {
	// This is our top-level arena for allocations that will last the lifetime of
	// the program
	Arena arena = arena_make();

	// We pass arena into get person so it can allocate and give that memory
	// back to us
	Person p = get_person(&arena);
	printf("name:%s, favorite int:%d\n", p.name, p.favorite_int);

	// We destroy the arena when we are done
	arena_destroy(&arena);
	return 0;
}

Person get_person(Arena *a) {
	// create a local "scratchpad" arena
	Arena local = arena_make();
	char *name = arena_allocate(1024, &local);
	char *favint = arena_allocate(64, &local);

	// get input from user
	printf("Enter your first name:");
	fgets(name, 1024, stdin);
	printf("Enter your favorite 32 bit integer:");
	fgets(favint, 64, stdin);

	// allocate only necessary memory for the user's name using the provided arena
	Person p = {0};
	p.name = arena_allocate(strlen(name), a); // <- allocation owned by main (yay!)
	strncpy(p.name, name, strlen(name)-1); // cut off the newline

	// convert the string to an integer
	p.favorite_int = atoi(favint);

	// get rid of our "scratchpad" allocations
	arena_destroy(&local);

	// return our Person with allocations that are owned by "main", the caller
	return p;
}
		

Notice how in get_person We create a "scratchpad" arena for allocations that will be temporary. Typically you only need one local arena for a function, but you could use more if you want fine-grained control.

Also notice how we use the a arena passed to get_person in order to allocate memory that will outlast the get_person function call.

Just by following this simple pattern we solve all of our problems.

It's now very simple to write c libraries and share them with people. It is also much simpler to write c programs without having to constantly worry about mis-matched lifetimes. There are some things I haven't addressed here, such as how to deal with memory shared between threads, but I just wanted to cover the idea of using "local" and "external" arenas to simplify memory management in C.

If you want to play around with this arena allocator, I've pushed it to a gitlab repo here. There are some interesting properties of my Arena implementation itself, but that is beyond the scope of this blog entry.