std.heap.FixedBufferAllocator em Zig — Referência e Exemplos

FixedBufferAllocator — Alocador sobre Buffer Fixo

O FixedBufferAllocator aloca memória a partir de um buffer pré-existente de tamanho fixo. Ele não faz nenhuma syscall ao sistema operacional, tornando-o ideal para sistemas embarcados, hot paths e situações onde a quantidade máxima de memória é conhecida em tempo de compilação.

Visão Geral

const std = @import("std");

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

Características:

  • Zero syscalls — opera inteiramente em memória do usuário
  • Alocação sequencial (bump allocator)
  • free individual não recupera memória (exceto o último bloco)
  • Determinístico e previsível
  • Ideal para buffers na stack ou em seções .data

Métodos

// Inicializa com um buffer existente
pub fn init(buf: []u8) FixedBufferAllocator

// Obtém a interface Allocator
pub fn allocator(self: *FixedBufferAllocator) std.mem.Allocator

// Reseta o alocador (todas as alocações são invalidadas)
pub fn reset(self: *FixedBufferAllocator) void

// Retorna a posição atual (bytes usados)
pub fn endIndex(self: FixedBufferAllocator) usize

Exemplo 1: Alocação Básica sem Syscalls

const std = @import("std");

pub fn main() !void {
    // Buffer na stack — sem nenhuma alocação dinâmica
    var buf: [1024]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buf);
    const allocator = fba.allocator();

    // Alocar dados
    const numeros = try allocator.alloc(u32, 10);
    for (numeros, 0..) |*n, i| {
        n.* = @intCast(i * 10);
    }

    const texto = try allocator.dupe(u8, "Olá, FixedBuffer!");

    std.debug.print("Números: ", .{});
    for (numeros) |n| {
        std.debug.print("{d} ", .{n});
    }
    std.debug.print("\nTexto: {s}\n", .{texto});
    std.debug.print("Bytes usados: {d}/{d}\n", .{ fba.endIndex(), buf.len });
}

Exemplo 2: Formatação sem Alocação Dinâmica

const std = @import("std");

fn formatarLog(buf: []u8, nivel: []const u8, mensagem: []const u8) []const u8 {
    var fba = std.heap.FixedBufferAllocator.init(buf);
    const allocator = fba.allocator();

    const resultado = std.fmt.allocPrint(allocator, "[{s}] {s}", .{
        nivel, mensagem,
    }) catch return "[ERRO] Buffer insuficiente";

    return resultado;
}

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

    const msg1 = formatarLog(&buf, "INFO", "Servidor iniciado na porta 8080");
    std.debug.print("{s}\n", .{msg1});

    // Reuso do buffer para outra mensagem
    const msg2 = formatarLog(&buf, "WARN", "Conexão lenta detectada");
    std.debug.print("{s}\n", .{msg2});
}

Exemplo 3: Estrutura de Dados com Memória Limitada

const std = @import("std");

const MAX_ITENS = 50;

const FilaFixa = struct {
    buf: [4096]u8 = undefined,
    fba: std.heap.FixedBufferAllocator = undefined,
    lista: std.ArrayList([]const u8) = undefined,

    fn init(self: *FilaFixa) void {
        self.fba = std.heap.FixedBufferAllocator.init(&self.buf);
        self.lista = std.ArrayList([]const u8).init(self.fba.allocator());
    }

    fn adicionar(self: *FilaFixa, item: []const u8) !void {
        if (self.lista.items.len >= MAX_ITENS) {
            return error.FilaCheia;
        }
        const copia = try self.fba.allocator().dupe(u8, item);
        try self.lista.append(copia);
    }

    fn listar(self: *FilaFixa) void {
        std.debug.print("Fila ({d} itens):\n", .{self.lista.items.len});
        for (self.lista.items, 0..) |item, i| {
            std.debug.print("  {d}. {s}\n", .{ i + 1, item });
        }
        std.debug.print("Memória usada: {d}/{d} bytes\n", .{
            self.fba.endIndex(), self.buf.len,
        });
    }

    fn limpar(self: *FilaFixa) void {
        self.fba.reset();
        self.lista = std.ArrayList([]const u8).init(self.fba.allocator());
    }
};

pub fn main() !void {
    var fila: FilaFixa = .{};
    fila.init();

    try fila.adicionar("Tarefa 1: Compilar projeto");
    try fila.adicionar("Tarefa 2: Executar testes");
    try fila.adicionar("Tarefa 3: Deploy para produção");

    fila.listar();

    // Reset e reuso
    fila.limpar();
    try fila.adicionar("Nova tarefa após reset");
    std.debug.print("\nApós reset:\n", .{});
    fila.listar();
}

Padrões Comuns

Buffer na Stack para Operações Temporárias

fn processar(dados: []const u8) ![]const u8 {
    var buf: [8192]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buf);
    const alloc = fba.allocator();
    // Usar alloc para trabalho temporário...
}

Tamanho do Buffer

Calcule o tamanho necessário considerando:

  • Tamanho dos dados
  • Overhead de alinhamento (cada alocação é alinhada)
  • Overhead de metadados do ArrayList/HashMap
// Para N inteiros de 32 bits:
const tamanho_buf = @sizeOf(u32) * N + 64; // margem para alinhamento
var buf: [tamanho_buf]u8 = undefined;

Tratamento de OutOfMemory

O FixedBufferAllocator retorna error.OutOfMemory quando o buffer está cheio:

const dados = allocator.alloc(u8, 10000) catch |err| {
    if (err == error.OutOfMemory) {
        std.debug.print("Buffer fixo esgotado!\n", .{});
    }
    return err;
};

Uso em Sistemas Embarcados

// Buffer global para sistema embarcado
var heap_buf: [16 * 1024]u8 = undefined;
var heap = std.heap.FixedBufferAllocator.init(&heap_buf);

export fn main() void {
    const alloc = heap.allocator();
    // Toda alocação vem deste buffer — sem SO necessário
}

Módulos Relacionados

Tutoriais e Receitas Relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.