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:
- O ponteiro é exatamente o que foi retornado por
alloc/realloc - O alocador usado para
freeé o mesmo que fez oalloc - O
freeé chamado apenas uma vez para cadaalloc - O slice passado tem o mesmo tamanho que o alocado originalmente
Erros Relacionados
- Double free — Liberar memória duas vezes
- Use after free — Usar memória após liberação
- OutOfMemory — Memória insuficiente
- Memory leak detected — Vazamento de memória