Memory Leak — Como Resolver em Zig
O Que Este Erro Significa
Um Memory Leak (vazamento de memória) ocorre quando o programa aloca memória dinamicamente mas nunca a libera. Com o tempo, a memória consumida cresce continuamente até causar OutOfMemory ou degradação de desempenho. Diferente de linguagens com garbage collector, em Zig toda alocação deve ser liberada manualmente.
O GeneralPurposeAllocator de Zig detecta vazamentos automaticamente no deinit():
error(gpa): memory address 0x7f1234560000 has not been freed
error(gpa): leaked allocation of 1024 bytes
O status retornado por gpa.deinit() indica se houve vazamento:
const status = gpa.deinit();
if (status == .leak) {
// Vazamento detectado!
}
Causas Comuns
1. Esquecer de Chamar free
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const dados = try allocator.alloc(u8, 1024);
// LEAK: nunca chama allocator.free(dados)
_ = dados;
}
2. Retorno Antecipado sem Liberar
const std = @import("std");
fn processar(allocator: std.mem.Allocator) !void {
const buffer = try allocator.alloc(u8, 1024);
const resultado = try fazerAlgo();
if (resultado < 0) {
return error.Invalido; // LEAK: buffer nunca é liberado!
}
allocator.free(buffer);
}
fn fazerAlgo() !i32 {
return -1;
}
3. Sobrescrever Ponteiro sem Liberar o Anterior
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var dados = try allocator.alloc(u8, 100);
// LEAK: o primeiro buffer é perdido!
dados = try allocator.alloc(u8, 200);
// Só o segundo será acessível para free
allocator.free(dados);
}
4. Esquecer deinit em 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);
try lista.append(1);
try lista.append(2);
try lista.append(3);
// LEAK: nunca chama lista.deinit()
}
5. Leak em Estruturas Aninhadas
const std = @import("std");
const Registro = struct {
nome: []u8,
email: []u8,
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var lista = std.ArrayList(Registro).init(allocator);
defer lista.deinit(); // Libera a lista, mas NÃO os campos internos!
const nome = try allocator.alloc(u8, 10);
@memcpy(nome, "Zig Brasil");
const email = try allocator.alloc(u8, 15);
@memcpy(email, "zig@brasil.dev.");
try lista.append(.{ .nome = nome, .email = email });
// LEAK: nome e email nunca são liberados!
}
Como Corrigir
Solucao 1: Usar defer Imediatamente Após Alocar
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const dados = try allocator.alloc(u8, 1024);
defer allocator.free(dados); // Garante free ao final do escopo
// Usa dados normalmente
dados[0] = 42;
}
Solucao 2: Usar errdefer para Caminhos de Erro
const std = @import("std");
fn processar(allocator: std.mem.Allocator) ![]u8 {
const buffer = try allocator.alloc(u8, 1024);
errdefer allocator.free(buffer); // Free SÓ se retornar erro
try validar(buffer);
return buffer; // Sucesso: transfere propriedade
}
fn validar(buf: []u8) !void {
_ = buf;
}
Solucao 3: Liberar Antes de Sobrescrever
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var dados = try allocator.alloc(u8, 100);
// Libera o antigo antes de realocar
allocator.free(dados);
dados = try allocator.alloc(u8, 200);
defer allocator.free(dados);
}
Solucao 4: Limpar Coleções com Dados Aninhados
const std = @import("std");
const Registro = struct {
nome: []u8,
email: []u8,
};
fn limparRegistros(allocator: std.mem.Allocator, lista: *std.ArrayList(Registro)) void {
for (lista.items) |reg| {
allocator.free(reg.nome);
allocator.free(reg.email);
}
lista.deinit();
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var lista = std.ArrayList(Registro).init(allocator);
defer limparRegistros(allocator, &lista);
// Agora ao sair, tudo é liberado corretamente
}
Solucao 5: ArenaAllocator para Alocações com Mesmo Ciclo de Vida
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 TUDO de uma vez — impossível vazar
const allocator = arena.allocator();
_ = try allocator.alloc(u8, 100);
_ = try allocator.alloc(u8, 200);
_ = try allocator.alloc(u8, 300);
// Sem free individual necessário — arena cuida de tudo
}
Detecção de Leaks
Usando GeneralPurposeAllocator
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{
.verbose_log = true, // Loga cada alocação e free
}){};
defer {
const status = gpa.deinit();
if (status == .leak) {
@panic("Vazamento de memória detectado!");
}
}
const allocator = gpa.allocator();
const buf = try allocator.alloc(u8, 100);
_ = buf;
// Se esquecer de liberar, o deinit reporta o leak
}
Usando Valgrind
zig build-exe main.zig
valgrind --leak-check=full ./main
Regras de Ouro
- Para cada
alloc, tenha umfreecorrespondente. - Use
deferimediatamente após cada alocação. - Use
errdeferpara liberar em caminhos de erro. - Cuidado com dados aninhados — libere de dentro para fora.
- Use ArenaAllocator quando múltiplas alocações compartilham o ciclo de vida.
- Sempre teste com GeneralPurposeAllocator em Debug para detectar leaks cedo.
Erros Relacionados
- OutOfMemory — Consequência de leaks acumulados
- Allocator leak detected — Detecção formal pelo alocador
- Use after free — Oposto do leak: usar após liberar
- Double free — Liberar a mesma memória duas vezes