Cheatsheet: Allocators em Zig
O sistema de alocadores é um dos pilares fundamentais de Zig. Em vez de um alocador global implícito como em C (malloc/free) ou um garbage collector como em Go/Java, Zig exige que você passe explicitamente um alocador para qualquer função que precise alocar memória. Isso dá controle total e facilita testes, depuração e otimização.
Conceito Fundamental
Em Zig, std.mem.Allocator é uma interface (implementada via ponteiro de função). Qualquer código que precisa alocar memória recebe um Allocator como parâmetro:
const std = @import("std");
fn criarLista(allocator: std.mem.Allocator) !std.ArrayList(u8) {
return std.ArrayList(u8).init(allocator);
}
Por que isso importa:
- Sem estado global oculto — cada alocação é explícita
- Fácil trocar alocadores para testes ou otimização
- Detecção automática de vazamento de memória em modo debug
- Permite alocadores especializados para cada caso de uso
Tipos de Alocadores
GeneralPurposeAllocator (GPA)
O alocador mais usado no dia a dia. Detecta vazamentos de memória e uso indevido (use-after-free, double-free) em modo debug.
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const status = gpa.deinit();
if (status == .leak) @panic("Vazamento de memória detectado!");
}
const allocator = gpa.allocator();
// Usar o allocator normalmente
const dados = try allocator.alloc(u8, 100);
defer allocator.free(dados);
// Alocar um único item
const ptr = try allocator.create(u32);
defer allocator.destroy(ptr);
ptr.* = 42;
}
Opções de configuração:
var gpa = std.heap.GeneralPurposeAllocator(.{
.stack_trace_frames = 8, // frames no stack trace de erros
.enable_memory_limit = true, // habilitar limite de memória
.thread_safe = true, // seguro para múltiplas threads
}){};
gpa.setRequestedMemoryLimit(1024 * 1024); // limite de 1MB
ArenaAllocator
Aloca tudo de uma vez e libera tudo junto. Excelente para operações com tempo de vida bem definido.
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
// Arena usa outro alocador como backend
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit(); // libera TUDO de uma vez
const allocator = arena.allocator();
// Não precisa de free individual!
const buf1 = try allocator.alloc(u8, 256);
const buf2 = try allocator.alloc(u8, 512);
const buf3 = try allocator.alloc(u8, 1024);
// Usar buf1, buf2, buf3...
_ = buf1;
_ = buf2;
_ = buf3;
// Resetar arena sem liberar a memória do SO
_ = arena.reset(.retain_capacity);
// Agora a memória pode ser reutilizada
}
Quando usar Arena:
- Processamento de requisições (alocar tudo, processar, liberar tudo)
- Parsing de dados temporários
- Operações batch que produzem resultado final
FixedBufferAllocator
Aloca memória a partir de um buffer fixo na stack ou pré-alocado. Zero alocações no 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();
// Aloca do buffer estático
const dados = try allocator.alloc(u8, 100);
_ = dados;
// Se ultrapassar 4096 bytes, retorna error.OutOfMemory
// Resetar para reusar
fba.reset();
}
Quando usar FixedBuffer:
- Sistemas embarcados sem heap
- Operações com tamanho máximo conhecido
- Quando não pode falhar na alocação (pré-aloca tudo)
page_allocator
Alocador que usa diretamente as chamadas de sistema do SO (mmap/VirtualAlloc). Cada alocação pede pelo menos uma página (geralmente 4KB).
const std = @import("std");
pub fn main() !void {
const allocator = std.heap.page_allocator;
// Cada alocação é no mínimo uma página do SO
const dados = try allocator.alloc(u8, 1024);
defer allocator.free(dados);
// Bom para alocações grandes
const grande = try allocator.alloc(u8, 1024 * 1024); // 1MB
defer allocator.free(grande);
}
c_allocator
Wrapper sobre malloc/free do C. Útil para interop com bibliotecas C.
const std = @import("std");
pub fn main() !void {
const allocator = std.heap.c_allocator;
const dados = try allocator.alloc(u8, 100);
defer allocator.free(dados);
}
Nota: Requer linkagem com libc (-lc ou const std_options = .{ .link_libc = true } no build.zig).
Operações do Allocator
Tabela de Referência Rápida
| Operação | Descrição | Exemplo |
|---|---|---|
alloc(T, n) | Alocar array de n elementos | try alloc.alloc(u8, 100) |
free(slice) | Liberar slice alocado | alloc.free(dados) |
create(T) | Alocar um único item | try alloc.create(Struct) |
destroy(ptr) | Liberar item único | alloc.destroy(ptr) |
realloc(slice, n) | Redimensionar alocação | try alloc.realloc(buf, 200) |
dupe(T, slice) | Duplicar slice | try alloc.dupe(u8, "cópia") |
Exemplos Detalhados
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
// alloc e free
const nums = try alloc.alloc(i32, 10);
defer alloc.free(nums);
for (nums, 0..) |*n, i| {
n.* = @intCast(i);
}
// create e destroy
const Ponto = struct { x: f32, y: f32 };
const p = try alloc.create(Ponto);
defer alloc.destroy(p);
p.* = .{ .x = 1.0, .y = 2.0 };
// dupe — cria cópia independente
const original = "texto original";
const copia = try alloc.dupe(u8, original);
defer alloc.free(copia);
// realloc — redimensionar
var buf = try alloc.alloc(u8, 10);
buf = try alloc.realloc(buf, 50); // agora tem 50 bytes
defer alloc.free(buf);
}
Padrão: Alocador como Parâmetro
A convenção em Zig é sempre receber o alocador como parâmetro:
const std = @import("std");
const MeuBuffer = struct {
dados: []u8,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator, tamanho: usize) !MeuBuffer {
return .{
.dados = try allocator.alloc(u8, tamanho),
.allocator = allocator,
};
}
pub fn deinit(self: *MeuBuffer) void {
self.allocator.free(self.dados);
}
};
Padrão: Defer para Limpeza
Sempre use defer para garantir a liberação:
fn processarDados(allocator: std.mem.Allocator) ![]u8 {
var temp = try allocator.alloc(u8, 1024);
defer allocator.free(temp); // liberado mesmo se houver erro
// ... processar usando temp ...
// Resultado separado que será retornado (caller faz free)
const resultado = try allocator.dupe(u8, temp[0..tamanho_real]);
return resultado;
}
Comparativo dos Alocadores
| Alocador | Velocidade | Segurança Debug | Uso Ideal |
|---|---|---|---|
GeneralPurposeAllocator | Médio | Alta (detecta leaks) | Desenvolvimento geral |
ArenaAllocator | Rápido | Média | Operações batch |
FixedBufferAllocator | Muito rápido | Baixa | Embarcados, stack |
page_allocator | Lento | Nenhuma | Alocações grandes |
c_allocator | Médio | Nenhuma | Interop com C |
Erros Comuns
// ERRO: esquecer de liberar memória
fn ruim(allocator: std.mem.Allocator) !void {
const dados = try allocator.alloc(u8, 100);
// Sem free! GPA vai detectar o vazamento
}
// CORRETO: usar defer
fn bom(allocator: std.mem.Allocator) !void {
const dados = try allocator.alloc(u8, 100);
defer allocator.free(dados);
// ...
}
// ERRO: usar memória após free (use-after-free)
fn perigoso(allocator: std.mem.Allocator) !void {
const dados = try allocator.alloc(u8, 100);
allocator.free(dados);
dados[0] = 42; // Comportamento indefinido!
}
Veja Também
- Error Handling — Tratamento de erros com allocators
- Structs — Structs que gerenciam memória
- Arena Pattern — Padrão de uso avançado de arenas
- Troubleshooting: Memory Leak — Como encontrar vazamentos
- std.mem na Biblioteca Padrão — Documentação completa de std.mem