Perguntas de Entrevista sobre Memória em Zig

Perguntas de Entrevista sobre Memória em Zig

Gerenciamento de memória é o tema mais frequente e mais importante em entrevistas de programação de sistemas. Zig adota uma abordagem única com seu sistema de allocators explícitos, que é tanto uma das maiores forças da linguagem quanto um dos conceitos mais testados em entrevistas. Domine cada pergunta desta página para se preparar adequadamente.

Conceitos Fundamentais

Explique o padrão de allocator em Zig. Por que Zig não usa malloc/free como C?

Em Zig, funções que precisam alocar memória recebem um Allocator como parâmetro explícito. Isso contrasta com C, onde malloc é uma função global com estado oculto.

fn criarLista(allocator: std.mem.Allocator) !std.ArrayList(u8) {
    var list = std.ArrayList(u8).init(allocator);
    try list.append(42);
    return list;
}

Benefícios do padrão allocator:

  • Testabilidade: Testes podem usar allocators que detectam leaks automaticamente.
  • Flexibilidade: Diferentes partes do programa podem usar estratégias de alocação diferentes.
  • Transparência: É explícito quando e onde alocação acontece — sem surpresas.
  • Performance: Permite usar allocators especializados (arena, pool) onde faz sentido.

Qual a diferença entre stack e heap allocation em Zig?

Stack: Memória alocada automaticamente para variáveis locais. Rápida (ajuste de ponteiro), mas limitada em tamanho e escopo. Variáveis morrem ao sair do bloco.

fn exemplo() void {
    var buffer: [1024]u8 = undefined; // stack
    // buffer é liberado automaticamente ao sair da função
}

Heap: Memória alocada dinamicamente via allocators. Persiste até ser explicitamente liberada. Mais lenta (syscalls), mas flexível em tamanho e lifetime.

fn exemplo(allocator: std.mem.Allocator) !void {
    const data = try allocator.alloc(u8, 1024); // heap
    defer allocator.free(data);
    // ...
}

Quais allocators a biblioteca padrão oferece e quando usar cada um?

  • page_allocator: Aloca diretamente do OS via mmap/VirtualAlloc. Simples mas lento para alocações pequenas. Use para grandes blocos ou quando não precisa de granularidade fina.

  • GeneralPurposeAllocator: Allocator de propósito geral com detecção de erros (double-free, use-after-free, leaks). Ideal para desenvolvimento e testes.

  • ArenaAllocator: Agrupa alocações e libera tudo de uma vez. Excelente para dados temporários com lifetime uniforme (ex: processar uma requisição HTTP).

  • FixedBufferAllocator: Aloca de um buffer pré-alocado na stack ou heap. Sem syscalls, sem fragmentação. Ideal para embedded e cenários de performance crítica.

  • c_allocator: Wrapper sobre malloc/free de libc. Útil para interop com C.

Perguntas Avançadas

O que é um memory leak e como Zig ajuda a detectá-los?

Memory leak ocorre quando memória alocada nunca é liberada. Em Zig, o GeneralPurposeAllocator em modo debug detecta leaks:

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

Além disso, o padrão defer/errdefer encoraja liberação de memória no ponto de alocação, reduzindo a probabilidade de leaks.

Explique o padrão defer para gerenciamento de recursos.

O padrão idiomático em Zig é parear cada alocação com um defer de liberação imediatamente:

const data = try allocator.alloc(u8, size);
defer allocator.free(data);
// usar data...

Para recursos que devem ser liberados apenas em caso de erro:

const data = try allocator.alloc(u8, size);
errdefer allocator.free(data);
// ... mais código que pode falhar ...
return data; // caller é responsável por liberar

Como implementar um arena allocator e quando usá-lo?

Um arena allocator aloca sequencialmente de um bloco grande e libera tudo de uma vez:

var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit(); // libera TUDO

const allocator = arena.allocator();
// Todas as alocações abaixo serão liberadas juntas
const a = try allocator.alloc(u8, 100);
const b = try allocator.alloc(u8, 200);
// Não precisa de free individual

Casos de uso: Processamento de requisições (cada requisição tem uma arena), compiladores (cada fase tem uma arena), game loops (dados temporários por frame).

O que é use-after-free e como Zig o previne?

Use-after-free ocorre quando código acessa memória que já foi liberada. Em Zig, o GeneralPurposeAllocator em modo debug preenche memória liberada com valores sentinela, causando crash previsível em vez de comportamento indefinido.

A linguagem também previne muitos cenários via análise em tempo de compilação — por exemplo, retornar ponteiro para variável local é erro de compilação.

Explique a diferença entre []T, [*]T, *T e *[N]T.

  • []T (slice): Ponteiro + comprimento. A forma mais segura e idiomática de referenciar dados contíguos.
  • [*]T (many-item pointer): Ponteiro para zero ou mais itens. Sem informação de comprimento — usado em interop com C.
  • *T (single-item pointer): Ponteiro para exatamente um item.
  • *[N]T (pointer to array): Ponteiro para um array de tamanho N conhecido em compilação.

Cenários Práticos

Como evitar fragmentação de memória em software de longo prazo?

Estratégias incluem:

  • Usar arena allocators para alocações de vida curta
  • Pool allocators para objetos de tamanho fixo
  • Pré-alocar buffers de tamanho conhecido
  • Evitar alocações no hot path
  • Monitorar com ferramentas de profiling

Como gerenciar memória em sistemas embarcados com Zig?

Em embedded com RAM limitada:

  • Use FixedBufferAllocator com buffer estático
  • Prefira alocação em stack quando possível
  • Evite alocação dinâmica no runtime para sistemas safety-critical
  • Use comptime para calcular tamanhos necessários em tempo de compilação

Preparação Complementar

Estude também:

Continue aprendendo Zig

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