Allocator Leak Detected — Como Resolver em Zig

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

Continue aprendendo Zig

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