Introdução
Vazamentos de memória (memory leaks) ocorrem quando seu programa aloca memória mas nunca a libera. Em Zig, o GeneralPurposeAllocator oferece detecção automática de vazamentos, reportando exatamente onde a memória foi alocada. Isso é uma das grandes vantagens do sistema de alocadores explícitos do Zig.
Nesta receita, você aprenderá a identificar, diagnosticar e corrigir vazamentos de memória.
Pré-requisitos
- Zig instalado (versão 0.13+). Veja o guia de instalação
- Conhecimento básico de Zig. Consulte a introdução ao Zig
- Familiaridade com GeneralPurposeAllocator
Detectar Vazamentos com GPA
O GPA detecta automaticamente memória não liberada ao chamar deinit():
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const status = gpa.deinit();
switch (status) {
.ok => std.debug.print("Sem vazamentos de memória!\n", .{}),
.leak => std.debug.print("ALERTA: Vazamento de memória detectado!\n", .{}),
}
}
const allocator = gpa.allocator();
// Alocação 1: corretamente liberada
const dados1 = try allocator.alloc(u8, 100);
defer allocator.free(dados1);
// Alocação 2: corretamente liberada
const dados2 = try allocator.alloc(i32, 50);
defer allocator.free(dados2);
std.debug.print("Programa executado com sucesso\n", .{});
}
Saída esperada
Programa executado com sucesso
Sem vazamentos de memória!
Exemplo de Vazamento e Correção
Identifique o problema e veja a correção:
const std = @import("std");
fn funcaoComVazamento(allocator: std.mem.Allocator) ![]u8 {
// ERRADO: alocação intermediária nunca liberada
const temp = try allocator.alloc(u8, 50);
@memset(temp, 'X');
// O resultado é uma nova alocação, mas 'temp' foi esquecido
const resultado = try allocator.dupe(u8, temp[0..10]);
// Faltou: allocator.free(temp);
return resultado;
}
fn funcaoCorrigida(allocator: std.mem.Allocator) ![]u8 {
// CORRETO: liberar alocação intermediária
const temp = try allocator.alloc(u8, 50);
defer allocator.free(temp); // Libera temp ao sair da função
@memset(temp, 'X');
const resultado = try allocator.dupe(u8, temp[0..10]);
return resultado;
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const status = gpa.deinit();
if (status == .leak) {
std.debug.print("Vazamento detectado!\n", .{});
} else {
std.debug.print("Sem vazamentos!\n", .{});
}
}
const allocator = gpa.allocator();
// Usar a versão corrigida
const resultado = try funcaoCorrigida(allocator);
defer allocator.free(resultado);
std.debug.print("Resultado: {s}\n", .{resultado});
}
Padrão errdefer para Evitar Vazamentos em Erros
const std = @import("std");
const Recurso = struct {
nome: []u8,
dados: []u8,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator, nome: []const u8, tamanho: usize) !Recurso {
const nome_copia = try allocator.dupe(u8, nome);
errdefer allocator.free(nome_copia); // Libera se a próxima alocação falhar
const dados = try allocator.alloc(u8, tamanho);
errdefer allocator.free(dados); // Libera se algo mais falhar
@memset(dados, 0);
return Recurso{
.nome = nome_copia,
.dados = dados,
.allocator = allocator,
};
}
pub fn deinit(self: *Recurso) void {
self.allocator.free(self.nome);
self.allocator.free(self.dados);
}
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const status = gpa.deinit();
if (status == .leak) {
std.debug.print("Vazamento detectado!\n", .{});
} else {
std.debug.print("Sem vazamentos!\n", .{});
}
}
const allocator = gpa.allocator();
var recurso = try Recurso.init(allocator, "meu-recurso", 256);
defer recurso.deinit();
std.debug.print("Recurso '{s}' criado com {d} bytes\n", .{ recurso.nome, recurso.dados.len });
}
Checklist Anti-Vazamento
Padrões que ajudam a prevenir vazamentos:
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Regra 1: defer imediato após alocação
const a = try allocator.alloc(u8, 100);
defer allocator.free(a);
// Regra 2: errdefer para funções que podem falhar
var lista = std.ArrayList(u8).init(allocator);
defer lista.deinit();
// Regra 3: deinit em structs que possuem memória
var mapa = std.StringHashMap(u32).init(allocator);
defer mapa.deinit();
// Regra 4: liberar resultado de toOwnedSlice
try lista.appendSlice("teste");
const owned = try lista.toOwnedSlice();
defer allocator.free(owned);
// Regra 5: liberar resultado de allocPrint
const msg = try std.fmt.allocPrint(allocator, "valor: {d}", .{42});
defer allocator.free(msg);
std.debug.print("Todas as alocações gerenciadas corretamente\n", .{});
std.debug.print(" a: {d} bytes\n", .{a.len});
std.debug.print(" owned: {s}\n", .{owned});
std.debug.print(" msg: {s}\n", .{msg});
}
Teste Automatizado para Vazamentos
const std = @import("std");
const testing = std.testing;
fn processarDados(allocator: std.mem.Allocator) ![]u8 {
var resultado = std.ArrayList(u8).init(allocator);
errdefer resultado.deinit();
try resultado.appendSlice("processado");
return resultado.toOwnedSlice();
}
test "processarDados não vaza memória" {
// testing.allocator é um GPA que falha o teste se houver vazamento
const resultado = try processarDados(testing.allocator);
defer testing.allocator.free(resultado);
try testing.expectEqualStrings("processado", resultado);
}
test "ArrayList sem vazamento" {
var lista = std.ArrayList(i32).init(testing.allocator);
defer lista.deinit();
try lista.append(1);
try lista.append(2);
try lista.append(3);
try testing.expectEqual(@as(usize, 3), lista.items.len);
}
Execute os testes com zig test arquivo.zig. O testing.allocator automaticamente detecta vazamentos e faz o teste falhar.
Dicas e Boas Práticas
deferimediato: Sempre coloquedefer free()na linha seguinte à alocação.errdeferem funções falíveis: Se uma função pode retornar erro após alocar, useerrdeferpara limpar.testing.allocatornos testes: Ele detecta vazamentos automaticamente nos testes unitários.GPA em modo debug: Use GPA durante desenvolvimento e troque para
page_allocatorouc_allocatorem produção.ArenaAllocator simplifica: Quando muitas alocações têm o mesmo tempo de vida, use ArenaAllocator.
Receitas Relacionadas
- Usando GeneralPurposeAllocator - Alocador com debug
- Usando ArenaAllocator - Prevenir vazamentos
- Errdefer Patterns - Padrões de limpeza
- Testes Unitários com Allocator - Testar memória