Skip to content

Fixed-Buffer Allocator

A fixed-buffer allocator uses memory that already exists.

A fixed-buffer allocator uses memory that already exists.

It does not request memory from the operating system. Instead, allocations come from a fixed block supplied by the program.

This is useful in embedded systems, kernels, bootloaders, games, temporary workspaces, and programs that must avoid heap allocation.

A fixed-buffer allocator begins with a buffer:

var buffer: [1024]u8 = undefined;

The allocator uses this memory for future allocations.

A complete example:

const std = @import("std");

pub fn main() !void {
    var memory: [1024]u8 = undefined;

    var fba = std.heap.FixedBufferAllocator.init(&memory);
    const allocator = fba.allocator();

    const bytes = try allocator.alloc(u8, 128);

    @memset(bytes, 0);

    std.debug.print("allocated {d} bytes\n", .{bytes.len});
}

The memory comes from:

var memory: [1024]u8 = undefined;

The allocator state is:

var fba = std.heap.FixedBufferAllocator.init(&memory);

The allocator interface is:

const allocator = fba.allocator();

Unlike the page allocator or general-purpose allocator, the fixed-buffer allocator cannot grow beyond its buffer.

If the buffer is exhausted, allocation fails:

const xs = try allocator.alloc(u8, 5000);

This may return error.OutOfMemory.

The allocator therefore behaves predictably. The program knows the exact maximum memory available to the allocator.

This is often important in constrained systems.

The fixed-buffer allocator also avoids operating-system allocation overhead. Allocation is usually very fast because the allocator only advances through the buffer.

A common use is temporary workspace memory:

var workspace: [4096]u8 = undefined;

var fba = std.heap.FixedBufferAllocator.init(&workspace);
const allocator = fba.allocator();

Functions can now allocate temporary objects without touching the system heap.

A fixed-buffer allocator is often combined with containers:

const std = @import("std");

pub fn main() !void {
    var memory: [256]u8 = undefined;

    var fba = std.heap.FixedBufferAllocator.init(&memory);
    const allocator = fba.allocator();

    var list = std.ArrayList(u8).init(allocator);
    defer list.deinit();

    try list.appendSlice("hello");
    try list.appendSlice(" zig");

    std.debug.print("{s}\n", .{list.items});
}

If the list grows beyond the available buffer, allocation fails.

This failure is explicit.

A fixed-buffer allocator is therefore useful for testing allocation behavior under strict limits.

For example:

var memory: [64]u8 = undefined;

Now large allocations fail quickly, which may expose assumptions hidden in the program.

The allocator itself does not own the buffer. The buffer must outlive all allocations made from it.

This is wrong:

fn makeAllocator() std.mem.Allocator {
    var memory: [1024]u8 = undefined;

    var fba = std.heap.FixedBufferAllocator.init(&memory);

    return fba.allocator(); // wrong
}

The returned allocator depends on memory and fba, both of which disappear when the function returns.

The backing storage must remain valid.

The fixed-buffer allocator usually works best when:

  • maximum memory usage is known
  • allocations are temporary
  • allocation speed matters
  • heap allocation is undesirable
  • deterministic memory usage is required

It works poorly when memory requirements are unpredictable or very large.

Unlike a general-purpose allocator, a fixed-buffer allocator cannot request more memory when exhausted.

The simple model is:

buffer -> allocator -> allocations

The allocator divides the buffer into pieces until no space remains.

Exercises:

Exercise 12-17. Create a fixed-buffer allocator backed by a 512-byte array and allocate two slices from it.

Exercise 12-18. Use std.ArrayList(u8) with a fixed-buffer allocator and append a short string.

Exercise 12-19. Create a very small fixed buffer and observe allocation failure.

Exercise 12-20. Explain why a fixed-buffer allocator is useful in embedded systems.