std.heap.GeneralPurposeAllocator em Zig — Referência e Exemplos

GeneralPurposeAllocator — Alocador de Uso Geral

O GeneralPurposeAllocator (GPA) é o alocador recomendado para uso geral em Zig. Ele combina bom desempenho com recursos de depuração poderosos, incluindo detecção de vazamentos de memória, double-free, uso após free e corrupção de heap.

Visão Geral

const std = @import("std");

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();

O GPA é o alocador ideal para:

  • Desenvolvimento e depuração
  • Aplicações de uso geral
  • Quando detecção de bugs de memória é desejada

Configuração

pub fn GeneralPurposeAllocator(comptime config: Config) type

pub const Config = struct {
    /// Ativar log detalhado de alocações
    verbose_log: bool = false,

    /// Número de stack frames a capturar para cada alocação
    stack_trace_frames: usize = 8,

    /// Manter metadados para detecção de use-after-free
    safety: bool = std.debug.runtime_safety,

    /// Não retornar memória ao SO (melhor detecção de bugs)
    never_unmap: bool = false,

    /// Colocar páginas de guarda entre alocações
    page_guard: bool = false,
};

Métodos

// Obtém a interface Allocator
pub fn allocator(self: *Self) std.mem.Allocator

// Libera recursos e reporta vazamentos
// Retorna .ok ou .leak
pub fn deinit(self: *Self) enum { ok, leak }

Exemplo 1: Uso Básico com Detecção de Vazamentos

const std = @import("std");

pub fn main() void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer {
        const resultado = gpa.deinit();
        if (resultado == .leak) {
            std.debug.print("AVISO: Vazamento de memória detectado!\n", .{});
        } else {
            std.debug.print("Memória OK: sem vazamentos.\n", .{});
        }
    }
    const allocator = gpa.allocator();

    // Alocação que é liberada corretamente
    const dados = allocator.alloc(u8, 256) catch {
        std.debug.print("Falha na alocação\n", .{});
        return;
    };
    defer allocator.free(dados);

    // Usar os dados
    @memset(dados, 'A');
    std.debug.print("Alocados {d} bytes: {s}...\n", .{ dados.len, dados[0..10] });
}

Exemplo 2: Configuração Avançada para Depuração

const std = @import("std");

pub fn main() void {
    // Configuração mais agressiva para encontrar bugs
    var gpa = std.heap.GeneralPurposeAllocator(.{
        .stack_trace_frames = 16,  // Mais frames no stack trace
        .never_unmap = true,       // Detecta use-after-free mais cedo
    }){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Simular uso típico de aplicação
    var lista = std.ArrayList([]const u8).init(allocator);
    defer {
        for (lista.items) |item| allocator.free(item);
        lista.deinit();
    }

    const nomes = [_][]const u8{ "Zig", "Rust", "Go", "C" };
    for (nomes) |nome| {
        const copia = allocator.dupe(u8, nome) catch continue;
        lista.append(copia) catch {
            allocator.free(copia);
            continue;
        };
    }

    std.debug.print("Linguagens:\n", .{});
    for (lista.items) |item| {
        std.debug.print("  - {s}\n", .{item});
    }
}

Exemplo 3: GPA como Alocador Base para Arena

const std = @import("std");

fn processarLote(allocator: std.mem.Allocator, items: []const u32) !u64 {
    // Arena para alocações temporárias deste lote
    var arena = std.heap.ArenaAllocator.init(allocator);
    defer arena.deinit();
    const temp = arena.allocator();

    // Alocações temporárias (liberadas automaticamente pelo arena)
    var resultados = try temp.alloc(u64, items.len);

    var soma: u64 = 0;
    for (items, 0..) |item, i| {
        resultados[i] = @as(u64, item) * @as(u64, item);
        soma += resultados[i];
    }

    return soma;
}

pub fn main() !void {
    // GPA como alocador base — detecta vazamentos do arena incorretamente usado
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer {
        const status = gpa.deinit();
        std.debug.print("Status da memória: {}\n", .{status});
    }
    const allocator = gpa.allocator();

    const items = [_]u32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    const resultado = try processarLote(allocator, &items);
    std.debug.print("Soma dos quadrados: {d}\n", .{resultado});
}

Padrões Comuns

Padrão de Inicialização Típico

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    try executar(allocator);
}

Verificação em Modo Release

Em builds de release, as verificações de segurança são desativadas por padrão para melhor desempenho:

// Em debug: detecção completa de bugs
// Em release: sem overhead de segurança
var gpa = std.heap.GeneralPurposeAllocator(.{
    .safety = true, // Forçar segurança mesmo em release
}){};

Alternativa para Produção

Para aplicações em produção onde o desempenho é crítico, considere usar std.heap.c_allocator ou page_allocator com arena:

const allocator = if (builtin.mode == .Debug)
    gpa.allocator()
else
    std.heap.c_allocator;

Considerações de Desempenho

O GPA tem overhead moderado em comparação com malloc:

  • Mantém metadados por alocação para detecção de bugs
  • Captura stack traces (configurável)
  • Verifica integridade em free()

Para código onde desempenho de alocação é crítico, considere ArenaAllocator ou page_allocator.

Módulos Relacionados

Tutoriais e Receitas Relacionados

Continue aprendendo Zig

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