OutOfMemory — Como Resolver em Zig
O Que Este Erro Significa
O erro OutOfMemory ocorre quando um alocador de memória em Zig não consegue satisfazer uma requisição de alocação. Diferente de linguagens com garbage collector, em Zig toda alocação de memória é explícita e pode falhar. Quando o sistema operacional não tem memória disponível suficiente, ou quando o alocador atinge um limite interno, a operação de alocação retorna error.OutOfMemory.
Este é um dos erros mais fundamentais em Zig, pois qualquer função que aloca memória pode potencialmente retorná-lo. Ele faz parte do error set de praticamente todas as funções da biblioteca padrão que fazem alocações dinâmicas.
Causas Comuns
1. Alocação de Memória Muito Grande
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Tentando alocar 1 TB de memória — provavelmente falhará
const buffer = try allocator.alloc(u8, 1_000_000_000_000);
// error.OutOfMemory
defer allocator.free(buffer);
}
2. Vazamento de Memória Acumulado
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
while (true) {
const dados = try allocator.alloc(u8, 1_000_000);
// ERRO: nunca chama allocator.free(dados)
// Memória vaza a cada iteração até OutOfMemory
_ = dados;
}
}
3. Alocador com Limite Fixo (FixedBufferAllocator)
const std = @import("std");
pub fn main() !void {
var buffer: [64]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
// Pede mais do que os 64 bytes disponíveis
const dados = try allocator.alloc(u8, 128);
// error.OutOfMemory — buffer fixo não tem espaço
defer allocator.free(dados);
}
4. Crescimento Descontrolado de ArrayList
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);
defer lista.deinit();
// Adicionando elementos indefinidamente
while (true) {
try lista.append(42);
// Eventualmente: error.OutOfMemory
}
}
5. Recursão que Aloca em Cada Nível
const std = @import("std");
fn processarRecursivo(allocator: std.mem.Allocator, profundidade: usize) !void {
const buffer = try allocator.alloc(u8, 1_000_000);
defer allocator.free(buffer);
if (profundidade > 0) {
// Cada nível de recursão mantém 1 MB alocado
try processarRecursivo(allocator, profundidade - 1);
}
}
Como Corrigir
Solucao 1: Tratar o Erro Explicitamente
const std = @import("std");
pub fn main() void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const buffer = allocator.alloc(u8, 1_000_000) catch |err| {
std.debug.print("Falha ao alocar memória: {}\n", .{err});
return;
};
defer allocator.free(buffer);
// Usa buffer normalmente
buffer[0] = 42;
}
Solucao 2: Liberar Memória Corretamente com defer
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);
defer {
for (lista.items) |item| {
allocator.free(item);
}
lista.deinit();
}
var i: usize = 0;
while (i < 100) : (i += 1) {
const item = try allocator.alloc(u8, 1024);
try lista.append(item);
}
}
Solucao 3: Pré-alocar Capacidade Conhecida
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Pré-aloca capacidade para evitar realocações frequentes
var lista = try std.ArrayList(u32).initCapacity(allocator, 10_000);
defer lista.deinit();
var i: u32 = 0;
while (i < 10_000) : (i += 1) {
lista.appendAssumeCapacity(i); // Não pode falhar — capacidade já reservada
}
}
Solucao 4: Usar ArenaAllocator para Alocações Temporárias
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
const allocator = arena.allocator();
// Múltiplas alocações sem precisar de free individual
const a = try allocator.alloc(u8, 1024);
const b = try allocator.alloc(u8, 2048);
const c = try allocator.alloc(u8, 4096);
_ = a;
_ = b;
_ = c;
// Tudo é liberado pelo arena.deinit()
}
Solucao 5: Limitar Tamanho de Alocação
const std = @import("std");
const MAX_BUFFER_SIZE = 10 * 1024 * 1024; // 10 MB
fn alocarBuffer(allocator: std.mem.Allocator, tamanho: usize) ![]u8 {
if (tamanho > MAX_BUFFER_SIZE) {
return error.TamanhoExcedido;
}
return allocator.alloc(u8, tamanho);
}
Diagnóstico com GeneralPurposeAllocator
O GeneralPurposeAllocator detecta vazamentos automaticamente:
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{
.verbose_log = true, // Loga cada alocação/free
}){};
defer {
const status = gpa.deinit();
if (status == .leak) {
std.debug.print("AVISO: vazamento de memória detectado!\n", .{});
}
}
const allocator = gpa.allocator();
const buffer = try allocator.alloc(u8, 100);
_ = buffer;
// Sem free — o deinit reportará o vazamento
}
Estratégias de Prevenção
- Sempre use
deferpara liberar memória logo após a alocação. - Prefira alocação na stack quando o tamanho é conhecido em tempo de compilação.
- Use
ArenaAllocatorpara muitas alocações temporárias com o mesmo ciclo de vida. - Valide tamanhos antes de alocar, especialmente com dados de entrada externa.
- Monitore uso de memória com o
GeneralPurposeAllocatorem modo verbose durante desenvolvimento.
Erros Relacionados
- Memory leak detected — Vazamento de memória detectado pelo alocador
- Invalid free — Tentativa de liberar ponteiro inválido
- Stack overflow — Estouro da pilha de execução
- Buffer overrun — Acesso além dos limites do buffer