OutOfMemory — Como Resolver em Zig

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

  1. Sempre use defer para liberar memória logo após a alocação.
  2. Prefira alocação na stack quando o tamanho é conhecido em tempo de compilação.
  3. Use ArenaAllocator para muitas alocações temporárias com o mesmo ciclo de vida.
  4. Valide tamanhos antes de alocar, especialmente com dados de entrada externa.
  5. Monitore uso de memória com o GeneralPurposeAllocator em modo verbose durante desenvolvimento.

Erros Relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.