Como Usar GeneralPurposeAllocator em Zig

Introdução

O GeneralPurposeAllocator (GPA) é o alocador de uso geral recomendado em Zig para desenvolvimento e depuração. Ele fornece detecção de erros como double-free, use-after-free e vazamentos de memória, além de stack traces para ajudar a encontrar bugs. Em produção, pode ser substituído por alocadores mais performáticos como std.heap.c_allocator.

Nesta receita, você aprenderá a usar o GPA e entenderá suas funcionalidades de debug.

Pré-requisitos

Uso Básico do GPA

const std = @import("std");

pub fn main() !void {
    // Criar GPA com configuração padrão
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer {
        const status = gpa.deinit();
        if (status == .leak) {
            std.debug.print("AVISO: Vazamento de memória detectado!\n", .{});
        }
    }

    const allocator = gpa.allocator();

    // Alocar memória
    const dados = try allocator.alloc(u8, 100);
    defer allocator.free(dados);

    // Usar a memória
    @memset(dados, 0);
    @memcpy(dados[0..5], "Zig!\n");

    std.debug.print("{s}", .{dados[0..5]});
    std.debug.print("Alocados {d} bytes\n", .{dados.len});
}

Alocar Tipos Específicos

const std = @import("std");

const Ponto = struct {
    x: f64,
    y: f64,

    pub fn distancia(self: Ponto, outro: Ponto) f64 {
        const dx = self.x - outro.x;
        const dy = self.y - outro.y;
        return @sqrt(dx * dx + dy * dy);
    }
};

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

    // Alocar um único struct
    const p1 = try allocator.create(Ponto);
    defer allocator.destroy(p1);
    p1.* = .{ .x = 3.0, .y = 4.0 };

    const p2 = try allocator.create(Ponto);
    defer allocator.destroy(p2);
    p2.* = .{ .x = 0.0, .y = 0.0 };

    const dist = p1.distancia(p2.*);
    std.debug.print("Ponto 1: ({d:.1}, {d:.1})\n", .{ p1.x, p1.y });
    std.debug.print("Ponto 2: ({d:.1}, {d:.1})\n", .{ p2.x, p2.y });
    std.debug.print("Distância: {d:.2}\n", .{dist});

    // Alocar slice de structs
    const pontos = try allocator.alloc(Ponto, 5);
    defer allocator.free(pontos);

    for (pontos, 0..) |*p, i| {
        const fi: f64 = @floatFromInt(i);
        p.* = .{ .x = fi * 2.0, .y = fi * 3.0 };
    }

    std.debug.print("\nPontos alocados:\n", .{});
    for (pontos, 0..) |p, i| {
        std.debug.print("  [{d}] ({d:.1}, {d:.1})\n", .{ i, p.x, p.y });
    }
}

Duplicar Dados

const std = @import("std");

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

    // Duplicar string
    const original = "Aprendendo Zig em português";
    const copia = try allocator.dupe(u8, original);
    defer allocator.free(copia);

    std.debug.print("Original: {s}\n", .{original});
    std.debug.print("Cópia:    {s}\n", .{copia});
    std.debug.print("São o mesmo ponteiro? {}\n", .{original.ptr == copia.ptr});

    // Duplicar slice de inteiros
    const nums = [_]i32{ 10, 20, 30, 40, 50 };
    const nums_copia = try allocator.dupe(i32, &nums);
    defer allocator.free(nums_copia);

    std.debug.print("Cópia dos números: ", .{});
    for (nums_copia) |n| std.debug.print("{d} ", .{n});
    std.debug.print("\n", .{});
}

Realocar Memória

const std = @import("std");

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

    // Alocar inicialmente
    var buffer = try allocator.alloc(u8, 10);
    @memset(buffer, 'A');
    std.debug.print("Tamanho inicial: {d}\n", .{buffer.len});

    // Realocar para mais espaço
    buffer = try allocator.realloc(buffer, 20);
    @memset(buffer[10..], 'B');
    std.debug.print("Após realloc: {d}\n", .{buffer.len});
    std.debug.print("Conteúdo: {s}\n", .{buffer});

    // Liberar
    allocator.free(buffer);
}

Detecção de Vazamentos

O GPA detecta memória não liberada:

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer {
        const status = gpa.deinit();
        if (status == .leak) {
            std.debug.print("\n=== VAZAMENTO DE MEMÓRIA DETECTADO ===\n", .{});
        } else {
            std.debug.print("\n=== Sem vazamentos! ===\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(u32, 50);
    defer allocator.free(dados2);

    std.debug.print("Todas as alocações serão liberadas corretamente\n", .{});
}

GPA com Configurações Customizadas

const std = @import("std");

pub fn main() !void {
    // GPA com configurações avançadas
    var gpa = std.heap.GeneralPurposeAllocator(.{
        .stack_trace_frames = 8, // Frames de stack trace para debug
        .enable_memory_limit = true, // Habilitar limite de memória
    }){};
    defer _ = gpa.deinit();

    // Definir limite de memória (opcional)
    gpa.setRequestedMemoryLimit(1024 * 1024); // 1MB máximo

    const allocator = gpa.allocator();

    // Tentar alocar dentro do limite
    const dados = try allocator.alloc(u8, 1000);
    defer allocator.free(dados);
    std.debug.print("Alocados 1000 bytes com sucesso\n", .{});

    // Verificar uso de memória
    std.debug.print("Memória usada aproximadamente: 1000 bytes\n", .{});
}

Padrão: Escolher Alocador em Tempo de Execução

const std = @import("std");

fn fazerTrabalho(allocator: std.mem.Allocator) !void {
    const dados = try allocator.alloc(u8, 256);
    defer allocator.free(dados);

    @memset(dados, 0);
    std.debug.print("Trabalho feito com {d} bytes\n", .{dados.len});
}

pub fn main() !void {
    // Em debug: GPA para detectar erros
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    // A função aceita qualquer alocador via interface
    try fazerTrabalho(gpa.allocator());

    // Em produção, poderia usar:
    // try fazerTrabalho(std.heap.c_allocator);
    // ou
    // try fazerTrabalho(std.heap.page_allocator);
}

Dicas e Boas Práticas

  1. Use GPA durante o desenvolvimento: A detecção de erros economiza horas de depuração.

  2. Sempre verifique deinit(): O retorno indica se houve vazamentos.

  3. defer imediato: Coloque defer allocator.free(x) logo após cada alocação.

  4. Aceite std.mem.Allocator como parâmetro: Funções que alocam devem receber o alocador, permitindo trocar em produção.

  5. page_allocator para produção: É mais simples e rápido quando não precisa de debug.

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

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