Encontrar Memory Leaks em Zig — Detectar Vazamentos de Memória
Memory leaks ocorrem quando memória é alocada mas nunca liberada. Em Zig, o sistema de allocators facilita a detecção e correção de vazamentos. Este guia mostra como encontrar e resolver memory leaks no seu código.
Usando GeneralPurposeAllocator para Detectar Leaks
O GeneralPurposeAllocator (GPA) é sua principal ferramenta. Em modo debug, ele rastreia todas as alocações e reporta leaks quando deinit() é chamado:
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: Memory leak detectado!\n", .{});
}
}
const allocator = gpa.allocator();
// Este código tem leak — alocou mas não liberou
const dados = try allocator.alloc(u8, 1024);
_ = dados;
// Faltou: allocator.free(dados);
}
A saída do GPA inclui o stack trace da alocação que vazou, mostrando exatamente onde a memória foi alocada.
Usando testing.allocator em Testes
Nos testes, std.testing.allocator falha automaticamente se detectar leaks:
test "sem memory leak" {
const allocator = std.testing.allocator;
const dados = try allocator.alloc(u8, 100);
defer allocator.free(dados); // Sem isso, o teste FALHA
// ...usar dados...
}
Se esquecer o defer allocator.free(dados), o teste reportará o leak com stack trace.
Padrões Comuns de Memory Leak
1. Esqueceu o defer free
// LEAK: alocou mas não liberou
fn processar(allocator: std.mem.Allocator) !void {
const buffer = try allocator.alloc(u8, 4096);
// ...usar buffer...
// FALTOU: defer allocator.free(buffer);
}
// CORRETO:
fn processar(allocator: std.mem.Allocator) !void {
const buffer = try allocator.alloc(u8, 4096);
defer allocator.free(buffer);
// ...usar buffer...
}
2. Return antes do defer
// LEAK: defer nunca é registrado se o early return acontecer antes
fn perigoso(allocator: std.mem.Allocator, cond: bool) ![]u8 {
const a = try allocator.alloc(u8, 100);
if (cond) return a; // OK — chamador libera
const b = try allocator.alloc(u8, 200);
defer allocator.free(a); // Se chegou aqui, 'a' é liberado
return b; // Chamador libera 'b'
}
3. Leak em ArrayList ou HashMap
// LEAK: deinit libera a estrutura, mas não os itens alocados internamente
var lista = std.ArrayList([]u8).init(allocator);
defer lista.deinit();
// Se cada item foi alocado separadamente, precisa liberar cada um:
defer {
for (lista.items) |item| allocator.free(item);
lista.deinit();
}
4. Leak ao substituir valor em HashMap
var map = std.StringHashMap([]u8).init(allocator);
defer map.deinit();
// Ao fazer put, o valor ANTERIOR é perdido se já existia
// Precisa verificar e liberar o anterior
const result = try map.fetchPut("chave", novo_valor);
if (result) |kv| {
allocator.free(kv.value); // Liberar o valor antigo
}
Usando Valgrind
Para análise mais profunda, use Valgrind com binários Zig compilados em Debug:
# Compilar em modo Debug
zig build
# Rodar com Valgrind
valgrind --leak-check=full ./zig-out/bin/meu-app
# Saída mostra:
# ==12345== LEAK SUMMARY:
# ==12345== definitely lost: 1,024 bytes in 1 blocks
# ==12345== ...com stack trace da alocação
Usando AddressSanitizer
Zig suporta compilação com sanitizers:
# Compilar com AddressSanitizer (detecta leaks, use-after-free, etc.)
zig build-exe src/main.zig -fsanitize=address
# Rodar normalmente — erros são reportados automaticamente
./main
Estratégias de Prevenção
- Regra do defer imediato — Sempre coloque
defer freena linha seguinte à alocação - Use ArenaAllocator para alocações temporárias — libera tudo de uma vez
- Testes com testing.allocator — Detectam leaks automaticamente
- GPA em desenvolvimento — Sempre use GPA durante desenvolvimento
- Ownership claro — Documente quem é responsável por liberar cada alocação
// ArenaAllocator para operações batch — sem leak possível
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit(); // Libera TUDO de uma vez
const temp = arena.allocator();
// Muitas alocações temporárias sem precisar de free individual
Checklist de Debugging
Quando encontrar um memory leak:
- Ative o GPA e rode o programa
- Leia o stack trace da alocação vazada
- Encontre a linha que aloca a memória
- Verifique se há
defer freecorrespondente - Verifique todos os caminhos de retorno (incluindo erros)
- Verifique se containers (ArrayList, HashMap) liberam seus itens
Veja Também
- FAQ Memória — Conceitos de gerenciamento de memória
- Crash em Runtime — Crashes relacionados a memória
- Debugar Segfaults — Use-after-free e acessos inválidos
- Ler Stack Traces — Interpretar stack traces
- Erros do Compilador — Erros de compilação relacionados a memória