Allocator Invalid Free — Como Resolver em Zig

Allocator Invalid Free — Como Resolver em Zig

O Que Este Erro Significa

O erro de free inválido ocorre quando o programa tenta liberar memória usando um ponteiro que não foi retornado por uma alocação válida, ou quando usa o alocador errado para liberar a memória. O GeneralPurposeAllocator do Zig detecta esses problemas em modo Debug e emite um panic com informações detalhadas.

Mensagens típicas:

thread 1 panic: Invalid free
error(gpa): Allocation does not belong to this allocator
error(gpa): freeing memory not allocated by this allocator

Este erro indica corrupção de memória potencial — liberar um ponteiro inválido pode destruir as estruturas internas do alocador e causar falhas cascata.

Causas Comuns

1. Liberar Ponteiro Modificado

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const buffer = try allocator.alloc(u8, 100);
    const ptr_modificado = buffer[10..]; // Slice no meio do buffer

    // PANIC: ptr_modificado não é o ponteiro original retornado por alloc
    allocator.free(ptr_modificado);
}

2. Liberar com Alocador Diferente

const std = @import("std");

pub fn main() !void {
    var gpa1 = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa1.deinit();

    var gpa2 = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa2.deinit();

    const buffer = try gpa1.allocator().alloc(u8, 100);
    // PANIC: alocado por gpa1, tentando liberar por gpa2
    gpa2.allocator().free(buffer);
}

3. Liberar Ponteiro da Stack

const std = @import("std");

pub fn main() void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    var local: [100]u8 = undefined;
    const slice: []u8 = &local;

    // PANIC: slice aponta para memória da stack, não do heap
    allocator.free(slice);
}

4. Liberar Literal de String

const std = @import("std");

pub fn main() void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const texto: []const u8 = "olá mundo";
    // PANIC: string literal está no segmento de dados, não no heap
    allocator.free(@constCast(texto));
}

5. Liberar com Tamanho Incorreto

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const buffer = try allocator.alloc(u8, 100);

    // Criar slice com tamanho diferente do alocado
    const errado = buffer[0..50]; // Apenas 50 bytes do original de 100
    // Dependendo do alocador, free com tamanho errado é inválido
    allocator.free(errado);
}

Como Corrigir

Solucao 1: Sempre Liberar o Ponteiro Original

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const buffer = try allocator.alloc(u8, 100);
    defer allocator.free(buffer); // O slice ORIGINAL

    // Trabalhe com sub-slices sem liberar
    const parte = buffer[10..50];
    parte[0] = 42;
    // buffer é liberado pelo defer — correto
}

Solucao 2: Manter Referência ao Alocador Correto

const std = @import("std");

const Recurso = struct {
    dados: []u8,
    allocator: std.mem.Allocator, // Guarda referência ao alocador

    fn init(allocator: std.mem.Allocator, tamanho: usize) !Recurso {
        return .{
            .dados = try allocator.alloc(u8, tamanho),
            .allocator = allocator,
        };
    }

    fn deinit(self: *Recurso) void {
        self.allocator.free(self.dados); // Usa o alocador correto
    }
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    var recurso = try Recurso.init(gpa.allocator(), 100);
    defer recurso.deinit();
}

Solucao 3: Distinguir Memória Alocada de Estática

const std = @import("std");

const Texto = struct {
    conteudo: []const u8,
    alocado: bool,
    allocator: ?std.mem.Allocator,

    fn deTextoEstatico(texto: []const u8) Texto {
        return .{
            .conteudo = texto,
            .alocado = false,
            .allocator = null,
        };
    }

    fn deTextoDinamico(allocator: std.mem.Allocator, texto: []const u8) !Texto {
        const copia = try allocator.dupe(u8, texto);
        return .{
            .conteudo = copia,
            .alocado = true,
            .allocator = allocator,
        };
    }

    fn deinit(self: *Texto) void {
        if (self.alocado) {
            if (self.allocator) |alloc| {
                alloc.free(@constCast(self.conteudo));
            }
        }
    }
};

Solucao 4: Usar ArenaAllocator para Evitar Free Individual

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(); // Único free — libera tudo

    const allocator = arena.allocator();

    // Sem necessidade de free individual — sem risco de invalid free
    const a = try allocator.alloc(u8, 100);
    const b = try allocator.alloc(u8, 200);
    _ = a;
    _ = b;
}

Solucao 5: Usar defer Imediatamente

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Padrão: alloc seguido imediatamente de defer free
    const dados = try allocator.alloc(u8, 100);
    defer allocator.free(dados);

    // Use dados livremente — defer garante o free correto
    @memset(dados, 0);
    dados[0] = 42;
}

Diagnóstico

Habilitar Log Detalhado do GPA

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{
        .verbose_log = true,        // Loga alocações e frees
        .safety = true,             // Verificações extras
    }){};
    defer _ = gpa.deinit();

    // O GPA agora imprime cada alocação e free com endereços
}

Regras de Free Válido

Um free é válido somente quando:

  1. O ponteiro é exatamente o que foi retornado por alloc/realloc
  2. O alocador usado para free é o mesmo que fez o alloc
  3. O free é chamado apenas uma vez para cada alloc
  4. O slice passado tem o mesmo tamanho que o alocado originalmente

Erros Relacionados

Continue aprendendo Zig

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