Introdução
O FixedBufferAllocator permite alocar memória a partir de um buffer pré-existente, sem recorrer ao heap. Isso é particularmente útil em sistemas embarcados, código com restrições de tempo real, ou quando você quer garantir que nenhuma alocação de heap aconteça.
Nesta receita, você aprenderá a usar o FixedBufferAllocator para diversas situações práticas.
Pré-requisitos
- Zig instalado (versão 0.13+). Veja o guia de instalação
- Conhecimento básico de Zig. Consulte a introdução ao Zig
Uso Básico
Alocar memória a partir de um buffer na stack:
const std = @import("std");
pub fn main() !void {
// Buffer fixo na stack
var buffer: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
// Alocar como qualquer outro alocador
const dados = try allocator.alloc(u8, 100);
@memset(dados, 'Z');
const numeros = try allocator.alloc(i32, 10);
for (numeros, 0..) |*n, i| {
n.* = @intCast(i * i);
}
std.debug.print("String: {s}\n", .{dados[0..3]});
std.debug.print("Números: ", .{});
for (numeros) |n| std.debug.print("{d} ", .{n});
std.debug.print("\n", .{});
// Verificar espaço restante
std.debug.print("Buffer total: {d} bytes\n", .{buffer.len});
std.debug.print("Posição atual: {d} bytes usados\n", .{fba.end_index});
}
Formatar Strings sem Heap
Use FixedBufferAllocator para formatar strings sem alocação dinâmica:
const std = @import("std");
pub fn main() !void {
var buffer: [256]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
const nome = "Maria";
const idade: u32 = 28;
const mensagem = try std.fmt.allocPrint(
allocator,
"Olá, {s}! Você tem {d} anos.",
.{ nome, idade },
);
std.debug.print("{s}\n", .{mensagem});
std.debug.print("Usados {d} de {d} bytes\n", .{ mensagem.len, buffer.len });
// Reset para reutilizar o buffer
fba.reset();
const outra = try std.fmt.allocPrint(
allocator,
"Temperatura: {d:.1}°C",
.{23.5},
);
std.debug.print("{s}\n", .{outra});
}
Tratar Falta de Espaço
const std = @import("std");
pub fn main() !void {
// Buffer pequeno para demonstrar o limite
var buffer: [64]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
// Primeira alocação: sucesso
const a = try allocator.alloc(u8, 30);
std.debug.print("Alocação 1: {d} bytes (OK)\n", .{a.len});
// Segunda alocação: sucesso
const b = try allocator.alloc(u8, 20);
std.debug.print("Alocação 2: {d} bytes (OK)\n", .{b.len});
// Terceira alocação: sem espaço
const c = allocator.alloc(u8, 30);
if (c) |dados| {
std.debug.print("Alocação 3: {d} bytes (OK)\n", .{dados.len});
} else |err| {
std.debug.print("Alocação 3: falhou com {}\n", .{err});
}
}
FixedBufferAllocator com ArrayList
Usar estruturas padrão sem heap:
const std = @import("std");
pub fn main() !void {
var buffer: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
// ArrayList usando buffer fixo
var lista = std.ArrayList(i32).init(allocator);
// Não precisa de defer deinit -- o buffer é na stack
for (0..20) |i| {
lista.append(@intCast(i * 3)) catch |err| {
std.debug.print("Parou no elemento {d}: {}\n", .{ i, err });
break;
};
}
std.debug.print("Elementos na lista: {d}\n", .{lista.items.len});
std.debug.print("Lista: ", .{});
for (lista.items) |n| std.debug.print("{d} ", .{n});
std.debug.print("\n", .{});
}
Padrão: Tentar Stack, Fallback para Heap
const std = @import("std");
fn processarDados(allocator: std.mem.Allocator, tamanho: usize) ![]u8 {
const dados = try allocator.alloc(u8, tamanho);
for (dados, 0..) |*b, i| {
b.* = @intCast(i % 256);
}
return dados;
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
// Tentar com buffer fixo para dados pequenos
var buffer: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const tamanho: usize = 500;
if (processarDados(fba.allocator(), tamanho)) |dados| {
std.debug.print("Processado na stack: {d} bytes\n", .{dados.len});
} else |_| {
// Fallback para heap se o buffer fixo não for suficiente
const dados = try processarDados(gpa.allocator(), tamanho);
defer gpa.allocator().free(dados);
std.debug.print("Processado no heap: {d} bytes\n", .{dados.len});
}
}
Dicas e Boas Práticas
Tamanho do buffer: Considere o alinhamento de memória. O FBA alinha alocações, então o espaço útil pode ser menor que o tamanho do buffer.
Use
reset()para reutilizar: Após processar, façafba.reset()para reaproveitar o buffer sem realocar.Free não libera no meio: O FixedBufferAllocator não suporta liberação individual eficiente. Use
reset()para liberar tudo.Ideal para dados temporários: Perfeito quando os dados não precisam sobreviver além do escopo da função.
Combine com ArenaAllocator:
ArenaAllocator.init(fba.allocator())dá o melhor dos dois mundos.
Receitas Relacionadas
- Usando ArenaAllocator - Alocação em lote
- Usando GeneralPurposeAllocator - Alocador de uso geral
- Detectar Vazamentos de Memória - Debug de memória
- Formatar Strings com std.fmt - Formatação de texto