Introdução
O ArenaAllocator é um dos alocadores mais úteis em Zig. Ele aloca memória de um alocador subjacente e libera tudo de uma vez quando o arena é destruído. Isso é perfeito para cenários onde várias alocações têm o mesmo tempo de vida – como parsing de dados, processamento de requisições ou construção de estruturas temporárias.
Nesta receita, você aprenderá quando e como usar o ArenaAllocator na prática.
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 do ArenaAllocator
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
// Criar arena com GPA como alocador subjacente
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit(); // Libera TUDO de uma vez
const allocator = arena.allocator();
// Alocar sem precisar de defer/free individual
const nome = try allocator.dupe(u8, "Zig Brasil");
const numeros = try allocator.alloc(u32, 100);
const mensagem = try std.fmt.allocPrint(allocator, "Olá, {s}!", .{nome});
// Usar normalmente
for (numeros, 0..) |*n, i| {
n.* = @intCast(i * 2);
}
std.debug.print("Nome: {s}\n", .{nome});
std.debug.print("Mensagem: {s}\n", .{mensagem});
std.debug.print("Primeiro número: {d}\n", .{numeros[0]});
std.debug.print("Último número: {d}\n", .{numeros[99]});
// Não precisa de free individual -- arena.deinit() cuida de tudo
}
Arena para Processamento de Dados
Ideal quando você processa dados em fases:
const std = @import("std");
const Registro = struct {
nome: []const u8,
valor: f64,
};
fn processarCSV(allocator: std.mem.Allocator, csv: []const u8) ![]Registro {
var registros = std.ArrayList(Registro).init(allocator);
var linhas = std.mem.splitScalar(u8, csv, '\n');
while (linhas.next()) |linha| {
if (linha.len == 0) continue;
var campos = std.mem.splitScalar(u8, linha, ',');
const nome_raw = campos.next() orelse continue;
const valor_raw = campos.next() orelse continue;
const nome = try allocator.dupe(u8, std.mem.trim(u8, nome_raw, " "));
const valor = std.fmt.parseFloat(f64, std.mem.trim(u8, valor_raw, " ")) catch continue;
try registros.append(.{ .nome = nome, .valor = valor });
}
return registros.toOwnedSlice();
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
// Arena para todo o processamento
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit();
const csv_data =
\\Produto A, 29.90
\\Produto B, 49.50
\\Produto C, 15.00
\\Produto D, 89.99
;
const registros = try processarCSV(arena.allocator(), csv_data);
std.debug.print("Registros processados:\n", .{});
var total: f64 = 0;
for (registros) |reg| {
std.debug.print(" {s}: R$ {d:.2}\n", .{ reg.nome, reg.valor });
total += reg.valor;
}
std.debug.print("Total: R$ {d:.2}\n", .{total});
// arena.deinit() libera tudo automaticamente
}
Reset para Reutilização
Use reset para liberar toda a memória e reusar o arena:
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit();
const allocator = arena.allocator();
// Fase 1: Processar dados
const dados1 = try allocator.alloc(u8, 1000);
@memset(dados1, 'A');
std.debug.print("Fase 1: Alocados {d} bytes\n", .{dados1.len});
// Liberar tudo da fase 1 e começar de novo
_ = arena.reset(.retain_capacity);
// Fase 2: Novos dados (reutiliza a memória)
const dados2 = try allocator.alloc(u8, 500);
@memset(dados2, 'B');
std.debug.print("Fase 2: Alocados {d} bytes\n", .{dados2.len});
// Fase 3
_ = arena.reset(.retain_capacity);
const dados3 = try allocator.alloc(u8, 200);
@memset(dados3, 'C');
std.debug.print("Fase 3: Alocados {d} bytes\n", .{dados3.len});
}
Arena para Construir Strings
Construir strings sem se preocupar em liberar cada pedaço:
const std = @import("std");
fn construirRelatorio(allocator: std.mem.Allocator, itens: []const []const u8) ![]const u8 {
var partes = std.ArrayList(u8).init(allocator);
const writer = partes.writer();
try writer.writeAll("=== RELATÓRIO ===\n\n");
for (itens, 1..) |item, i| {
try writer.print("{d}. {s}\n", .{ i, item });
}
try writer.print("\nTotal: {d} itens\n", .{itens.len});
try writer.writeAll("=================\n");
return partes.toOwnedSlice();
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit();
const itens = [_][]const u8{
"Estudar Zig",
"Implementar servidor",
"Escrever testes",
"Revisar código",
};
const relatorio = try construirRelatorio(arena.allocator(), &itens);
std.debug.print("{s}", .{relatorio});
}
Quando Usar ArenaAllocator
| Cenário | Recomendado? |
|---|---|
| Parsing de dados | Sim |
| Processamento de requisição HTTP | Sim |
| Construção de strings temporárias | Sim |
| Objetos com tempos de vida diferentes | Não |
| Alocações de longa duração | Não |
| Cache em memória | Não |
Dicas e Boas Práticas
Um
defer arena.deinit()substitui muitosdefer free(): Essa é a grande vantagem – simplificação do gerenciamento de memória.Use com GPA em desenvolvimento:
ArenaAllocator.init(gpa.allocator())permite detectar bugs durante o desenvolvimento.reset(.retain_capacity)reutiliza memória: Não libera para o OS, mas permite reutilizar os blocos alocados.Não misture tempos de vida: Se alguns dados precisam sobreviver mais que outros, use alocadores separados.
Arena dentro de arena: Você pode criar sub-arenas para escopos mais curtos dentro de um processamento maior.
Receitas Relacionadas
- Usando GeneralPurposeAllocator - Alocador de uso geral
- Usando FixedBufferAllocator - Alocação sem heap
- Detectar Vazamentos de Memória - Debug de memória
- Como parsear JSON em Zig - Parsing com arena