General Purpose Allocator em Zig — O que é e Como Usar
Definição
O General Purpose Allocator (GPA, std.heap.GeneralPurposeAllocator) é o alocador de uso geral da biblioteca padrão do Zig. Ele combina performance razoável com detecção integrada de bugs de memória: vazamentos (memory leaks), double-free, use-after-free e overflows de buffer. É o alocador recomendado para a maioria das aplicações e especialmente para desenvolvimento e testes.
Por que o GPA Importa
- Detecção de bugs: Em modo Debug, detecta automaticamente os erros de memória mais comuns.
- Uso geral: Funciona bem para a maioria dos padrões de alocação.
- Configurável: Diversas opções para ajustar comportamento e segurança.
- Production-ready: Pode ser usado em produção com configurações de release.
Exemplo Prático
Uso Básico com Detecção de Leaks
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const status = gpa.deinit();
if (status == .leak) {
std.debug.print("ALERTA: Vazamento de memória detectado!\n", .{});
}
}
const allocator = gpa.allocator();
// Alocação normal
const dados = try allocator.alloc(u8, 100);
defer allocator.free(dados);
// Se esquecêssemos o defer acima, gpa.deinit() detectaria o leak
std.debug.print("Alocado {} bytes\n", .{dados.len});
}
Configuração Personalizada
const std = @import("std");
var gpa = std.heap.GeneralPurposeAllocator(.{
// Registra stack traces de alocação para debug
.stack_trace_frames = 8,
// Não libera memória de volta ao SO (mais rápido para debug)
.retain_metadata = true,
// Desabilita safety checks para release
.safety = true,
}){};
Usando em Testes
const std = @import("std");
const testing = std.testing;
fn criarLista(allocator: std.mem.Allocator) !std.ArrayList(u32) {
var lista = std.ArrayList(u32).init(allocator);
try lista.append(1);
try lista.append(2);
try lista.append(3);
return lista;
}
test "sem memory leak" {
// testing.allocator já é um GPA com detecção de leaks!
var lista = try criarLista(testing.allocator);
defer lista.deinit();
try testing.expectEqual(@as(usize, 3), lista.items.len);
// Se esquecer o defer, o teste FALHA automaticamente
}
Detectando Double Free
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const dados = try allocator.alloc(u8, 100);
allocator.free(dados);
// allocator.free(dados); // PANIC: double free detectado!
}
GPA como Alocador Principal da Aplicação
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var app = try App.init(allocator);
defer app.deinit();
try app.run();
}
O que o GPA Detecta
| Bug | Detecção | Modo |
|---|---|---|
| Memory leak | No deinit() | Debug + ReleaseSafe |
| Double free | No free() | Debug + ReleaseSafe |
| Use-after-free | No acesso | Debug |
| Buffer overflow | Na escrita | Debug |
Armadilhas Comuns
- Esquecer de chamar
deinit(): Semdeinit(), a detecção de leaks não ocorre. Sempre usedefer _ = gpa.deinit(). - Performance em hot paths: O GPA é mais lento que alocadores especializados. Para hot paths, considere
ArenaAllocator. - Ignorar o status de deinit: O retorno de
deinit()informa se houve leak. Verifique-o. - Usar em freestanding: O GPA depende do sistema operacional. Em embarcados, use
FixedBufferAllocator. - Confundir com
testing.allocator:testing.allocatorjá é um GPA configurado para testes. Não crie outro dentro de testes.
Quando Usar o GPA
O GPA é o alocador correto para a maioria das aplicações, mas existem situações onde outros alocadores se encaixam melhor:
| Situação | Alocador recomendado |
|---|---|
| Desenvolvimento e testes | GPA (detecção de bugs) |
| Aplicação geral em produção | GPA |
| Processamento de requisição HTTP | ArenaAllocator |
| Hot path com muitas alocações pequenas | ArenaAllocator |
| Ambiente embarcado sem SO | FixedBufferAllocator |
| Teste unitário | testing.allocator (já é um GPA) |
Uma prática comum em código Zig de produção é usar o GPA como alocador raiz e delegar para alocadores especializados (como ArenaAllocator) em contextos específicos:
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Para cada requisição, usa uma arena filha
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
const req_alloc = arena.allocator();
try processarRequisicao(req_alloc);
}
Boas Práticas
- Use
defer _ = gpa.deinit(): O underscore descarta o valor de retorno deliberadamente quando você não quer checar o status de leak. - Verifique o status em testes: Em testes, cheque explicitamente se o deinit retorna
.okpara garantir que não há vazamentos. - Não compartilhe o GPA entre threads sem sincronização: O GPA não é thread-safe por padrão. Use sincronização externa ou um allocator por thread.
Termos Relacionados
- Allocator — Interface de alocação de memória
- Arena Allocator — Alocador para liberação em lote
- Page Allocator — Alocador baseado em páginas
- Fixed Buffer Allocator — Alocador com buffer fixo
- Memory Leak — Vazamentos de memória