Memory Leak em Zig — O que é e Como Evitar
Definição
Um memory leak (vazamento de memória) ocorre quando um programa aloca memória no heap mas nunca a libera. Com o tempo, a memória disponível diminui, podendo levar a lentidão, OutOfMemory e, eventualmente, falha total do programa.
Em Zig, onde o gerenciamento de memória é manual, memory leaks são um risco real. Porém, a linguagem oferece ferramentas excelentes para detectá-los e preveni-los: o padrão defer e o GeneralPurposeAllocator com detecção integrada.
Por que Memory Leaks Importam
- Degradação progressiva: O programa fica cada vez mais lento até falhar.
- Servidores de longa duração: Leaks pequenos se acumulam ao longo de horas/dias.
- Recursos limitados: Em embarcados, qualquer leak é crítico.
- Difícil diagnóstico: Leaks muitas vezes só se manifestam em produção.
Exemplo Prático
Memory Leak Simples
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const status = gpa.deinit();
if (status == .leak) @panic("LEAK DETECTADO!");
}
const allocator = gpa.allocator();
// LEAK! Alocamos mas nunca liberamos
const dados = try allocator.alloc(u8, 1024);
_ = dados;
// Ao sair, gpa.deinit() detecta o leak
}
Corrigido com Defer
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); // Agora está correto!
// Usar dados...
}
Leak em Estruturas Aninhadas
const std = @import("std");
const Usuario = struct {
nome: []u8,
email: []u8,
allocator: std.mem.Allocator,
pub fn deinit(self: *Usuario) void {
self.allocator.free(self.nome);
self.allocator.free(self.email);
}
};
fn criarUsuario(allocator: std.mem.Allocator) !Usuario {
const nome = try allocator.dupe(u8, "Maria Silva");
errdefer allocator.free(nome); // Libera nome se email falhar
const email = try allocator.dupe(u8, "maria@email.com");
// Sem errdefer para email — se chegarmos aqui, tudo OK
return Usuario{
.nome = nome,
.email = email,
.allocator = allocator,
};
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var usuario = try criarUsuario(gpa.allocator());
defer usuario.deinit(); // Libera TODOS os campos internos
}
Detecção em Testes
const std = @import("std");
const testing = std.testing;
test "função não vaza memória" {
// testing.allocator detecta leaks automaticamente
var lista = std.ArrayList(u8).init(testing.allocator);
defer lista.deinit(); // Sem isto, o teste FALHA
try lista.appendSlice("Zig Brasil");
try testing.expectEqual(@as(usize, 10), lista.items.len);
}
Padrões de Prevenção
| Padrão | Descrição |
|---|---|
defer free | Liberar imediatamente após alocar |
errdefer free | Liberar em caso de erro parcial |
deinit() em structs | Métodos que liberam todos os campos |
| Arena Allocator | Liberar tudo de uma vez |
| GPA em testes | Detecção automática em cada teste |
Causas Comuns de Leaks
// 1. Sobrescrever ponteiro sem liberar
var ptr = try allocator.alloc(u8, 100);
ptr = try allocator.alloc(u8, 200); // LEAK: primeiro alloc perdido!
// 2. Return antecipado sem cleanup
const dados = try allocator.alloc(u8, 100);
if (condicao) return error.Falha; // LEAK: dados não liberados
allocator.free(dados);
// 3. Exceção em lista de inicialização
// (Usar errdefer para proteger)
Usando o Arena Allocator para Eliminar Leaks
O ArenaAllocator é uma estratégia poderosa: você aloca livremente, e libera tudo de uma vez ao final. É ideal para processamento de requisições, parsing ou qualquer fluxo com tempo de vida bem definido:
const std = @import("std");
pub fn processarRequisicao(backing: std.mem.Allocator) !void {
var arena = std.heap.ArenaAllocator.init(backing);
defer arena.deinit(); // Libera TUDO que foi alocado dentro
const allocator = arena.allocator();
// Pode alocar à vontade — tudo vai ser liberado junto
const nome = try allocator.dupe(u8, "Maria");
const sobrenome = try allocator.dupe(u8, "Silva");
const completo = try std.fmt.allocPrint(allocator, "{s} {s}", .{ nome, sobrenome });
std.debug.print("Nome: {s}\n", .{completo});
// Nenhum free individual necessário!
}
Com o Arena, a preocupação com leaks individuais desaparece — basta garantir o defer arena.deinit().
Boas Práticas
deferlogo após a alocação: Odefer allocator.free(dados)deve aparecer imediatamente na linha seguinte à alocação, antes de qualquer código que possa falhar.deinit()para structs com estado interno: Todo tipo que aloca internamente (ArrayList, HashMap, StringHashMap) deve ter umdeinit()correspondente chamado comdefer.- Teste com
testing.allocator: Em testes unitários, usestd.testing.allocator— ele acusa qualquer leak ao final do teste, tornando a verificação automática. - Arena para escopos de curta duração: Para processamento de uma requisição ou parsing de um documento, um Arena elimina a necessidade de rastrear cada alocação individualmente.
Armadilhas Comuns
- Confiar apenas em
defer: Se a alocação e a liberação estão em funções diferentes,defernão resolve. Implementedeinit(). - ArrayList/HashMap sem
deinit: Coleções da std guardam memória internamente. Sempre chamedeinit(). - Leak em loops: Alocar dentro de um loop sem liberar a cada iteração acumula leaks.
- Ignorar erros de alocação parcial: Sem
errdefer, falhas no meio de inicialização vazam recursos já alocados.
Termos Relacionados
- Allocator — Interface de alocação de memória
- Defer — Limpeza garantida ao sair do escopo
- Errdefer — Limpeza condicional em caso de erro
- General Purpose Allocator — GPA com detecção de leaks
- Dangling Pointer — Ponteiros para memória liberada