Allocator Leak Detected — Como Resolver em Zig
O Que Este Erro Significa
O aviso “leak detected” é emitido pelo GeneralPurposeAllocator (GPA) quando seu método deinit() é chamado e ainda existem alocações que nunca foram liberadas. Diferente de um crash, este é um diagnóstico que o GPA fornece para ajudar a encontrar e corrigir vazamentos de memória durante o desenvolvimento.
A saída típica:
error(gpa): memory address 0x7f5a12340000 leaked
error(gpa): leaked allocation of 1024 bytes at src/main.zig:15
O GPA rastreia cada alocação e seu stack trace de origem, fornecendo informação precisa sobre onde a memória foi alocada mas nunca liberada.
Causas Comuns
1. Esquecer de Chamar deinit em Coleções
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const status = gpa.deinit();
if (status == .leak) std.debug.print("LEAK!\n", .{});
}
const allocator = gpa.allocator();
var lista = std.ArrayList(u32).init(allocator);
try lista.append(1);
try lista.append(2);
// LEAK: nunca chamou lista.deinit()
}
2. Retorno Antecipado sem Cleanup
const std = @import("std");
fn processar(allocator: std.mem.Allocator) !void {
const buf = try allocator.alloc(u8, 1024);
if (true) { // Alguma condição
return error.Falha; // LEAK: buf não foi liberado!
}
allocator.free(buf);
}
3. HashMap/ArrayHashMap sem deinit
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var mapa = std.StringHashMap(u32).init(allocator);
try mapa.put("chave", 42);
// LEAK: mapa.deinit() nunca chamado
}
4. Esquecer de Liberar Valores dentro de Coleções
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var lista = std.ArrayList([]u8).init(allocator);
defer lista.deinit(); // Libera a lista, mas NÃO os itens
try lista.append(try allocator.alloc(u8, 100)); // Item 1
try lista.append(try allocator.alloc(u8, 200)); // Item 2
// LEAK: os itens individuais nunca são liberados
}
5. Erro em Cadeia de Alocações
const std = @import("std");
fn criarRecurso(allocator: std.mem.Allocator) !void {
const a = try allocator.alloc(u8, 100);
const b = try allocator.alloc(u8, 200); // Se falhar, 'a' vaza
const c = try allocator.alloc(u8, 300); // Se falhar, 'a' e 'b' vazam
allocator.free(c);
allocator.free(b);
allocator.free(a);
}
Como Corrigir
Solucao 1: defer e errdefer para Cada Alocação
const std = @import("std");
fn criarRecurso(allocator: std.mem.Allocator) !void {
const a = try allocator.alloc(u8, 100);
errdefer allocator.free(a);
const b = try allocator.alloc(u8, 200);
errdefer allocator.free(b);
const c = try allocator.alloc(u8, 300);
defer allocator.free(c);
// Processamento...
defer allocator.free(b);
defer allocator.free(a);
}
Solucao 2: Limpar Coleções com Dados Aninhados
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var lista = std.ArrayList([]u8).init(allocator);
defer {
// Libera cada item ANTES de liberar a lista
for (lista.items) |item| {
allocator.free(item);
}
lista.deinit();
}
try lista.append(try allocator.alloc(u8, 100));
try lista.append(try allocator.alloc(u8, 200));
}
Solucao 3: Sempre Chamar deinit em Structs com Estado
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var mapa = std.StringHashMap(u32).init(allocator);
defer mapa.deinit();
var lista = std.ArrayList(u8).init(allocator);
defer lista.deinit();
var buf_writer = std.ArrayList(u8).init(allocator);
defer buf_writer.deinit();
}
Solucao 4: ArenaAllocator para Ciclo de Vida Compartilhado
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(); // Libera ABSOLUTAMENTE tudo — zero leaks possíveis
const allocator = arena.allocator();
var lista = std.ArrayList([]u8).init(allocator);
// Sem defer lista.deinit() necessário — arena cuida
try lista.append(try allocator.alloc(u8, 100));
try lista.append(try allocator.alloc(u8, 200));
// Sem free individual necessário — arena cuida
}
Solucao 5: Verificar Leaks em Testes
const std = @import("std");
test "sem vazamento de memória" {
// testing.allocator detecta leaks automaticamente
const allocator = std.testing.allocator;
var lista = std.ArrayList(u32).init(allocator);
defer lista.deinit();
try lista.append(42);
try std.testing.expectEqual(@as(u32, 42), lista.items[0]);
// Se houver leak, o teste FALHA automaticamente
}
Padrão de Teste Anti-Leak
O std.testing.allocator é a ferramenta mais poderosa para detectar leaks:
const std = @import("std");
const MinhaStruct = struct {
dados: []u8,
allocator: std.mem.Allocator,
fn init(allocator: std.mem.Allocator) !MinhaStruct {
return .{
.dados = try allocator.alloc(u8, 100),
.allocator = allocator,
};
}
fn deinit(self: *MinhaStruct) void {
self.allocator.free(self.dados);
}
};
test "MinhaStruct não vaza memória" {
var s = try MinhaStruct.init(std.testing.allocator);
defer s.deinit();
// Se deinit não liberar tudo, o teste falha com:
// "Test failure: memory leak detected"
}
Diagnóstico Avançado
Habilitar Stack Traces para Alocações
var gpa = std.heap.GeneralPurposeAllocator(.{
.stack_trace_frames = 10, // Captura 10 frames de stack
.verbose_log = true, // Log detalhado
}){};
Quando um leak é detectado, o GPA mostra exatamente onde a memória foi alocada:
error(gpa): leaked allocation of 1024 bytes
src/main.zig:15:35 in main
src/lib.zig:42:20 in criarBuffer
Erros Relacionados
- Memory leak — Conceito geral de vazamento
- OutOfMemory — Consequência de leaks acumulados
- Double free — Liberar memória duas vezes
- Invalid free — Liberar ponteiro inválido