Allocator em Zig — O que é e Como Usar
Definição
Um Allocator (alocador) em Zig é uma abstração que encapsula a lógica de alocação e desalocação de memória. Diferentemente de linguagens como C, onde você usa diretamente malloc e free, ou de linguagens com garbage collector como Java e Go, Zig oferece uma interface padronizada (std.mem.Allocator) que permite trocar a estratégia de alocação sem alterar o código que a consome.
Em termos simples, o allocator é um “objeto” que sabe como pedir memória ao sistema e devolvê-la. Toda função da biblioteca padrão que precisa alocar memória recebe um allocator como parâmetro — essa é uma decisão de design fundamental da linguagem.
Por que Allocators Importam
- Controle explícito: Você sempre sabe onde e quando memória é alocada.
- Testabilidade: Em testes, você pode usar um allocator que detecta memory leaks automaticamente.
- Performance: Diferentes situações pedem diferentes estratégias (arena, pool, page).
- Sem alocação oculta: Nenhuma função aloca memória “por baixo dos panos” sem que você saiba.
Exemplo Prático
const std = @import("std");
pub fn main() !void {
// Criando um General Purpose Allocator
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const status = gpa.deinit();
if (status == .leak) @panic("Vazamento de memória detectado!");
}
const allocator = gpa.allocator();
// Alocando uma lista dinâmica
var lista = std.ArrayList(u32).init(allocator);
defer lista.deinit();
try lista.append(10);
try lista.append(20);
try lista.append(30);
for (lista.items) |valor| {
std.debug.print("Valor: {}\n", .{valor});
}
}
Neste exemplo, o GeneralPurposeAllocator é criado, e sua interface allocator() é passada para o ArrayList. O defer garante que a memória será liberada ao final do escopo.
Como Funciona a Interface
A interface std.mem.Allocator define três operações fundamentais:
// Simplificação da interface
pub const Allocator = struct {
allocFn: fn (self: *anyopaque, len: usize, ...) ?[*]u8,
resizeFn: fn (self: *anyopaque, buf: []u8, new_len: usize, ...) ?usize,
freeFn: fn (self: *anyopaque, buf: []u8, ...) void,
};
Qualquer tipo que implemente essas funções pode ser usado como allocator. Isso permite criar alocadores personalizados para situações específicas.
Armadilhas Comuns
- Esquecer o
defer deinit(): Sempre que alocar, garanta a desalocação comdefer. Sem isso, você terá vazamentos de memória. - Usar o allocator errado: Em contextos de alta performance com muitas alocações curtas, prefira um
ArenaAllocatorem vez doGeneralPurposeAllocator. - Misturar allocators: Não aloque com um allocator e desaloque com outro. O comportamento é indefinido.
- Ignorar falhas de alocação:
alloc()retorna um error union. Sempre trate o possível erro comtryoucatch.
Tipos de Allocator Disponíveis
| Allocator | Uso recomendado |
|---|---|
GeneralPurposeAllocator | Uso geral, com detecção de bugs |
ArenaAllocator | Muitas alocações, uma única liberação |
FixedBufferAllocator | Sem alocação do sistema, usa buffer fixo |
page_allocator | Alocações grandes, alinhadas a páginas |
Termos Relacionados
- Arena Allocator — Alocador que libera tudo de uma vez
- Page Allocator — Alocador baseado em páginas do SO
- General Purpose Allocator — Alocador de uso geral
- Memory Leak — Vazamentos de memória
- Stack vs Heap — Diferença entre pilha e heap