Encontrar Memory Leaks em Zig — Detectar Vazamentos de Memória

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

  1. Regra do defer imediato — Sempre coloque defer free na linha seguinte à alocação
  2. Use ArenaAllocator para alocações temporárias — libera tudo de uma vez
  3. Testes com testing.allocator — Detectam leaks automaticamente
  4. GPA em desenvolvimento — Sempre use GPA durante desenvolvimento
  5. 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:

  1. Ative o GPA e rode o programa
  2. Leia o stack trace da alocação vazada
  3. Encontre a linha que aloca a memória
  4. Verifique se há defer free correspondente
  5. Verifique todos os caminhos de retorno (incluindo erros)
  6. Verifique se containers (ArrayList, HashMap) liberam seus itens

Veja Também

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.