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)
freeindividual 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
- std.mem.Allocator — Interface Allocator
- std.heap — Visão geral dos alocadores
- page_allocator — Alocador com syscalls
- ArenaAllocator — Arena (similar mas com crescimento)
- std.io.FixedBufferStream — I/O sobre buffer fixo