Skip to main content

Overview

Zig provides several heap allocators optimized for different use cases. These allocators implement the standard Allocator interface and manage memory from the operating system.

DebugAllocator (GeneralPurposeAllocator)

A safe allocator designed for development and debugging. Detects memory leaks, double frees, and use-after-free bugs.
GeneralPurposeAllocator is deprecated and renamed to DebugAllocator.

Features

  • Memory leak detection with stack traces
  • Double-free detection with allocation and free stack traces
  • Use-after-free protection (never reuses addresses)
  • Alignment validation
  • Configurable memory limits
  • Thread-safe (configurable)
  • Cross-platform

Basic Usage

const std = @import("std");

var gpa = std.heap.DebugAllocator(.{}){};
defer {
    const check = gpa.deinit();
    if (check == .leak) {
        @panic("memory leak detected");
    }
}

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

Configuration

pub const Config = struct {
    /// Number of stack frames to capture (default: 6 in Debug, 0 in Release)
    stack_trace_frames: usize = default_stack_trace_frames,
    
    /// Enable memory limit tracking
    enable_memory_limit: bool = false,
    
    /// Enable safety checks (default: runtime_safety)
    safety: bool = std.debug.runtime_safety,
    
    /// Thread safety (default: !single_threaded)
    thread_safe: bool = !builtin.single_threaded,
    
    /// Custom mutex type
    MutexType: ?type = null,
    
    /// Never unmap memory (helps debug segfaults)
    never_unmap: bool = false,
    
    /// Retain metadata for better double-free detection
    retain_metadata: bool = false,
    
    /// Log all allocations
    verbose_log: bool = false,
    
    /// Does backing allocator return zeroed memory?
    backing_allocator_zeroes: bool = true,
    
    /// Refresh stack traces on resize
    resize_stack_traces: bool = false,
    
    /// Page size for small allocations
    page_size: usize = default_page_size,
};
Example with configuration:
var gpa = std.heap.DebugAllocator(.{
    .stack_trace_frames = 10,
    .enable_memory_limit = true,
    .safety = true,
}){};
defer _ = gpa.deinit();

const allocator = gpa.allocator();
// Set memory limit
gpa.requested_memory_limit = 1024 * 1024; // 1 MB

Methods

init

pub const init: DebugAllocator(config) = .{};
Default initialization value.

allocator

pub fn allocator(self: *DebugAllocator(config)) Allocator
Returns the standard Allocator interface.

deinit

pub fn deinit(self: *DebugAllocator(config)) std.heap.Check
Frees all allocator resources and checks for memory leaks.
return
std.heap.Check
  • .ok if no leaks detected
  • .leak if memory leaks were found
Example:
const check = gpa.deinit();
if (check == .leak) {
    std.log.err("Memory leaks detected!", .{});
}

detectLeaks

pub fn detectLeaks(self: *DebugAllocator(config)) usize
Manually detect and log memory leaks.
return
usize
Number of leaks detected.

Memory Layout

DebugAllocator divides allocations into two categories: Small allocations (< page_size / 2):
  • Grouped into size-class buckets (powers of 2)
  • Stored in fixed-size pages
  • Fast allocation with O(1) complexity
  • Includes metadata for debugging
Large allocations (≥ page_size / 2):
  • Allocated directly from backing allocator
  • Stored in a hash map
  • Supports efficient resize/remap operations

Performance Characteristics

DebugAllocator is designed for safety, not performance. Overhead includes:
  • Stack trace capture on every allocation
  • Metadata storage (> 100 bytes per small allocation)
  • Hash map lookups for large allocations
  • Memory is never reused (helps catch use-after-free)
Use DebugAllocator in Debug builds and switch to faster allocators in Release builds.

PageAllocator

Direct syscall-based allocator that requests memory from the OS.

Usage

const allocator = std.heap.page_allocator;
const data = try allocator.alloc(u8, 4096);
defer allocator.free(data);

Characteristics

  • Thread-safe: Can be used from multiple threads
  • Slow: Makes a syscall for every allocation
  • Aligned: All allocations are page-aligned (typically 4KB)
  • Zero-initialized: Memory is zeroed by the OS
  • Direct mapping: No internal fragmentation

Platform Support

  • Linux/Unix: Uses mmap/munmap
  • Windows: Uses VirtualAlloc/VirtualFree
  • WebAssembly: Uses memory.grow
  • Plan 9: Uses sbrk
Example:
// Allocate exactly 2 pages
const page_size = std.heap.pageSize();
const pages = try std.heap.page_allocator.alloc(u8, page_size * 2);
defer std.heap.page_allocator.free(pages);

C Allocator

Wraps C’s malloc, free, and realloc.

c_allocator

const allocator = std.heap.c_allocator;
Full Allocator interface with alignment support. Example:
const allocator = std.heap.c_allocator;
const data = try allocator.alignedAlloc(u8, .@"16", 100);
defer allocator.free(data);

raw_c_allocator

const allocator = std.heap.raw_c_allocator;
Direct malloc/free wrapper. Alignment limited to @alignOf(std.c.max_align_t). Use case: Backing allocator for ArenaAllocator.
var arena = std.heap.ArenaAllocator.init(std.heap.raw_c_allocator);
defer arena.deinit();

Characteristics

  • Fast: Optimized C implementation
  • Thread-safe: Platform-dependent
  • Platform-specific: Behavior varies by libc
  • Requires libc: Only available when builtin.link_libc == true

FixedBufferAllocator

Allocates from a fixed-size buffer. Fast but limited.

Usage

var buffer: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();

const data = try allocator.alloc(u8, 100);
// No free needed - memory is in buffer

Methods

init

pub fn init(buffer: []u8) FixedBufferAllocator
Creates allocator from buffer.

reset

pub fn reset(self: *FixedBufferAllocator) void
Resets allocator to reuse buffer.
fba.reset();
// Can now allocate again
const new_data = try allocator.alloc(u8, 100);

Characteristics

  • Fast: Bump allocator (O(1) allocation)
  • No free: Individual frees do nothing
  • No growth: Returns OutOfMemory when buffer full
  • Not thread-safe

WasmAllocator

Optimized for WebAssembly targets.
const allocator = std.heap.wasm_allocator;

Characteristics

  • WebAssembly-specific: Uses memory.grow instruction
  • Small and fast: Optimized for code size
  • Page-aligned: 64KB page size
  • Future default: Will be used by DebugAllocator in ReleaseSmall mode for wasm

ThreadSafeAllocator

Wraps any allocator to make it thread-safe.
var base = std.heap.FixedBufferAllocator.init(&buffer);
var tsa = std.heap.ThreadSafeAllocator{ .child_allocator = base.allocator() };
const allocator = tsa.allocator();

When to Use

  • Wrapping non-thread-safe allocators for concurrent access
  • Adding mutex protection to custom allocators
Many allocators (PageAllocator, c_allocator, DebugAllocator) are already thread-safe.

StackFallbackAllocator

Tries stack buffer first, falls back to heap allocator.
var stack_fallback = std.heap.stackFallback(4096, std.heap.page_allocator);
const allocator = stack_fallback.get();

const small = try allocator.alloc(u8, 100);  // Uses stack
const large = try allocator.alloc(u8, 5000); // Falls back to page_allocator

defer allocator.free(small);
defer allocator.free(large);

Characteristics

  • Hybrid: Fast for small allocations, flexible for large ones
  • Automatic: Transparently switches between stack and heap
  • One-time use: Call .get() only once

Choosing an Allocator

DebugAllocator - Catches bugs with detailed diagnostics
var gpa = std.heap.DebugAllocator(.{}){};
defer _ = gpa.deinit();
page_allocator - Simple, no cleanup needed
const allocator = std.heap.page_allocator;
ArenaAllocator (see next section) or c_allocator
const allocator = std.heap.c_allocator;
FixedBufferAllocator - No heap, predictable
var buffer: [8192]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
wasm_allocator - Optimized for size and WASM semantics
const allocator = std.heap.wasm_allocator;

See Also

Build docs developers (and LLMs) love