FAQ Gerenciamento de Memória em Zig

FAQ Gerenciamento de Memória em Zig

Zig usa gerenciamento manual de memória com um sistema de allocators que torna explícita toda alocação. Aqui estão as perguntas mais frequentes sobre como trabalhar com memória em Zig.

1. Por que Zig não tem garbage collector?

Zig foi projetado para programação de sistemas, onde o controle total sobre a memória é essencial. Um garbage collector introduz pausa imprevisíveis, overhead de memória e dificulta o uso em sistemas embarcados ou de tempo real. Com allocators explícitos, você sabe exatamente quando e onde a memória é alocada e liberada, resultando em código mais previsível e eficiente.

2. O que é um allocator?

Um allocator é um objeto que gerencia alocação e liberação de memória. Em Zig, toda alocação de memória dinâmica recebe um allocator como parâmetro. Isso permite:

  • Trocar estratégias de alocação sem mudar o código
  • Testar com allocators que detectam leaks
  • Otimizar para cenários específicos (arena, pool, etc.)
// Qualquer função que aloca recebe o allocator
fn carregarDados(allocator: std.mem.Allocator) ![]u8 {
    return allocator.alloc(u8, 1024);
}

3. Quais allocators existem na standard library?

AllocatorUso Recomendado
page_allocatorSimples, aloca páginas do SO diretamente
GeneralPurposeAllocatorUso geral com detecção de leaks em debug
ArenaAllocatorAlocar bastante, liberar tudo de uma vez
FixedBufferAllocatorAlocar dentro de um buffer fixo pré-existente
c_allocatorWrapper sobre malloc/free do C
testing.allocatorPara testes, detecta leaks automaticamente
// GeneralPurposeAllocator — mais comum para aplicações
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit(); // Reporta leaks em debug
const allocator = gpa.allocator();

4. Qual a diferença entre stack e heap?

  • Stack: Memória automática, rápida, tamanho limitado. Variáveis locais vivem aqui. Liberada automaticamente quando a função retorna.
  • Heap: Memória dinâmica, mais lenta, tamanho flexível. Gerenciada via allocators. Você é responsável por liberar.
fn exemplo(allocator: std.mem.Allocator) !void {
    // Stack — automático, rápido
    var buffer_stack: [256]u8 = undefined;
    _ = &buffer_stack;

    // Heap — manual, flexível
    const buffer_heap = try allocator.alloc(u8, 256);
    defer allocator.free(buffer_heap);
}

5. O que defer tem a ver com memória?

defer é a ferramenta principal para garantir que memória seja liberada. Ele executa a expressão quando o escopo atual termina, independentemente de como (retorno normal ou erro):

const dados = try allocator.alloc(u8, 1024);
defer allocator.free(dados); // SEMPRE será liberado

// ...usar dados...
// Mesmo que um erro ocorra aqui, free será chamado

Regra de ouro: sempre coloque o defer imediatamente após a alocação. Assim você nunca esquece de liberar.

6. O que é um memory leak e como Zig ajuda a encontrar?

Memory leak ocorre quando você aloca memória mas nunca a libera. Em Zig, o GeneralPurposeAllocator detecta leaks automaticamente em modo debug:

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
    const status = gpa.deinit();
    if (status == .leak) @panic("Memory leak detectado!");
}
const allocator = gpa.allocator();

const dados = try allocator.alloc(u8, 100);
// Esqueceu de liberar! O GPA reportará o leak com stack trace

Em testes, use std.testing.allocator que falha automaticamente se houver leak.

7. O que é ArenaAllocator e quando usar?

O ArenaAllocator permite fazer muitas alocações e liberá-las todas de uma vez. Ideal para processamento em batch ou quando muitos objetos temporários são criados:

var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit(); // Libera TUDO de uma vez

const alloc = arena.allocator();

// Muitas alocações sem precisar de defer individual
const a = try alloc.alloc(u8, 100);
const b = try alloc.alloc(u8, 200);
const c = try alloc.alloc(u8, 300);
// Não precisa liberar individualmente — arena.deinit() cuida de tudo
_ = a; _ = b; _ = c;

8. Ponteiros em Zig são iguais aos de C?

São similares, mas com mais segurança. Zig tem vários tipos de ponteiros:

  • *T — Ponteiro para um único elemento (não pode ser null)
  • [*]T — Ponteiro para muitos elementos
  • ?*T — Ponteiro opcional (pode ser null)
  • *const T — Ponteiro para dado imutável
  • []T — Slice (ponteiro + tamanho)
var x: i32 = 42;
const ptr: *i32 = &x;   // ponteiro para x
ptr.* = 100;            // dereferenciar e alterar

// Slices são a forma segura de trabalhar com arrays
const arr = [_]i32{ 1, 2, 3 };
const slice: []const i32 = &arr; // slice do array

Diferente de C, ponteiros em Zig não podem ser null por padrão (use ?*T para nullable).

9. O que é undefined e quando usar?

undefined é um valor que indica “não inicializado”. É usado quando você vai preencher os dados depois:

var buffer: [1024]u8 = undefined; // Não inicializado
const bytes_lidos = try arquivo.read(&buffer);
// Agora buffer[0..bytes_lidos] tem dados válidos

Em modo debug, undefined é preenchido com 0xAA, facilitando a detecção de uso de memória não inicializada. Nunca leia de uma variável undefined sem antes inicializá-la.

10. Como evitar use-after-free em Zig?

Use-after-free ocorre quando você acessa memória que já foi liberada. Zig ajuda de várias formas:

  • defer garante que a liberação acontece no momento certo
  • GeneralPurposeAllocator detecta use-after-free em debug (preenchendo memória liberada com padrão especial)
  • Disciplina de ownership — cada alocação tem um “dono” claro que é responsável pela liberação
// ERRADO: retornando ponteiro para memória que será liberada
fn perigoso(allocator: std.mem.Allocator) ![]u8 {
    const temp = try allocator.alloc(u8, 100);
    defer allocator.free(temp); // libera antes de retornar!
    return temp; // PERIGO: use-after-free
}

// CORRETO: quem chama é responsável por liberar
fn correto(allocator: std.mem.Allocator) ![]u8 {
    return allocator.alloc(u8, 100); // chamador faz defer free
}

11. Posso usar Zig sem alocação dinâmica?

Sim. Muitos projetos embarcados usam Zig sem heap. Você pode usar apenas a stack e buffers fixos:

// Sem alocação dinâmica
var buffer: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();

// Aloca dentro do buffer fixo — sem heap
const dados = try allocator.alloc(u8, 100);
_ = dados;

12. Como funciona @memcpy e @memset?

São builtins para operações rápidas de memória:

var destino: [100]u8 = undefined;
const origem = "Dados para copiar";

// Copiar memória
@memcpy(destino[0..origem.len], origem);

// Preencher memória
@memset(&destino, 0); // Zerar tudo

Veja Também

Continue aprendendo Zig

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