Skip to content

Page Allocator

The page allocator gets memory from the operating system.

The page allocator gets memory from the operating system.

It is available as:

std.heap.page_allocator

A page is a block of memory managed by the operating system. The exact size depends on the target. Common systems use pages of 4096 bytes, but a program should not depend on that unless it has checked the target.

The page allocator is simple to use:

const std = @import("std");

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    const bytes = try allocator.alloc(u8, 100);
    defer allocator.free(bytes);

    @memset(bytes, 0);

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

This program asks for 100 bytes.

The allocator may reserve more memory internally, because the operating system usually works in pages. The program still receives a slice of length 100.

The caller must free the slice with the same allocator:

defer allocator.free(bytes);

The page allocator is useful for small programs, examples, and code that does only a few large allocations.

It is not always a good allocator for many small allocations.

For example, this program allocates many tiny slices:

const std = @import("std");

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    var i: usize = 0;
    while (i < 1000) : (i += 1) {
        const p = try allocator.alloc(u8, 8);
        defer allocator.free(p);
    }
}

Each request may be expensive compared with the eight bytes requested. The allocator may need to ask the operating system for memory many times.

For many small allocations, use another allocator on top of page allocation, such as the general-purpose allocator or an arena allocator.

The page allocator has one advantage: it has little policy.

It does not try to organize a large number of application objects. It mostly exposes operating-system memory allocation in allocator form.

This makes it useful as a base allocator:

var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();

const allocator = arena.allocator();

Here the arena gets large chunks from the page allocator, then serves smaller allocations from those chunks.

The page allocator is also useful when the program deliberately wants memory in large independent regions.

For example:

const buffer = try std.heap.page_allocator.alloc(u8, 1024 * 1024);
defer std.heap.page_allocator.free(buffer);

This allocates one megabyte.

The page allocator can fail. It returns allocation errors through the allocator interface:

const memory = try allocator.alloc(u8, n);

The try is not decoration. It is part of the contract. Allocation is a fallible operation.

For short examples, page_allocator is acceptable. For long-running programs, libraries, and tests, it is usually better to accept an allocator from the caller.

Do not write this in a reusable library:

fn makeBuffer() ![]u8 {
    return try std.heap.page_allocator.alloc(u8, 1024);
}

This hides the allocation policy and makes ownership less visible.

Prefer this:

fn makeBuffer(allocator: std.mem.Allocator) ![]u8 {
    return try allocator.alloc(u8, 1024);
}

Now the caller chooses the allocator and owns the returned memory.

The page allocator is a tool, not a default for every program.

Use it when direct operating-system allocation is acceptable. Pass an allocator when writing code meant to be reused.

Exercises:

Exercise 12-5. Allocate 4096 bytes with std.heap.page_allocator, fill them with zero, and free them.

Exercise 12-6. Write a function that takes an allocator and a length, then returns a byte buffer of that length.

Exercise 12-7. Change the function from Exercise 12-6 so the caller uses std.heap.page_allocator.

Exercise 12-8. Explain why std.heap.page_allocator is usually not the best choice for thousands of small allocations.