Stack vs Heap em Zig — O que é e Como Usar

Stack vs Heap em Zig — O que é e Como Usar

Definição

Stack (pilha) e Heap (monte) são as duas regiões principais de memória onde dados podem ser armazenados durante a execução de um programa Zig.

  • Stack: Memória rápida, automática, com tamanho fixo, alocada e liberada em ordem LIFO (último a entrar, primeiro a sair). Variáveis locais vivem na stack.
  • Heap: Memória dinâmica, gerenciada manualmente via allocators, com tamanho flexível. Dados que precisam sobreviver além do escopo da função vivem no heap.

Em Zig, diferente de linguagens com garbage collector, você controla explicitamente quando dados vão para o heap.

Por que Entender Stack vs Heap Importa

  1. Performance: Stack é ordens de magnitude mais rápida que heap (alocação = mover ponteiro vs syscall).
  2. Tempo de vida: Dados na stack morrem ao sair do escopo; dados no heap vivem até serem liberados.
  3. Tamanho: Stack é limitada (tipicamente 8MB); heap é limitada pela RAM disponível.
  4. Previsibilidade: Stack não fragmenta; heap pode fragmentar com muitas alocações/liberações.

Exemplo Prático

Dados na Stack

const std = @import("std");

pub fn main() void {
    // Todas estas variáveis vivem na stack
    var x: u32 = 42;
    var buffer: [256]u8 = undefined;
    const ponto = struct { x: f64, y: f64 }{ .x = 1.0, .y = 2.0 };

    x += 1;
    buffer[0] = 'A';
    std.debug.print("x={}, ponto=({d},{d})\n", .{ x, ponto.x, ponto.y });
    // Ao sair de main, tudo é liberado automaticamente
}

Dados no Heap

const std = @import("std");

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

    // Alocação no heap — tamanho dinâmico
    const tamanho: usize = obterTamanho(); // valor só conhecido em runtime
    const buffer = try allocator.alloc(u8, tamanho);
    defer allocator.free(buffer);

    // Lista que cresce dinamicamente
    var lista = std.ArrayList(u32).init(allocator);
    defer lista.deinit();

    try lista.append(1);
    try lista.append(2);
    try lista.append(3);

    std.debug.print("Itens: {}\n", .{lista.items.len});
}

fn obterTamanho() usize {
    return 1024;
}

Retornando Dados: Stack vs Heap

// FUNCIONA: retorna cópia do valor (struct pequena)
fn criarPonto(x: f64, y: f64) struct { x: f64, y: f64 } {
    return .{ .x = x, .y = y };
}

// PERIGOSO: retornar ponteiro para variável local!
fn perigoso() *u32 {
    var local: u32 = 42;
    return &local; // ERRO: dangling pointer!
}

// CORRETO: alocar no heap quando precisa do ponteiro
fn seguro(allocator: std.mem.Allocator) !*u32 {
    const ptr = try allocator.create(u32);
    ptr.* = 42;
    return ptr; // OK: dados vivem no heap
}

Comparação Detalhada

CaracterísticaStackHeap
VelocidadeMuito rápidaMais lenta
TamanhoLimitado (~8MB)Limitado pela RAM
GerenciamentoAutomáticoManual (allocator)
FragmentaçãoNenhumaPossível
Tamanho dos dadosFixo em compilaçãoDinâmico em runtime
Thread safetyCada thread tem sua stackCompartilhado entre threads
Localidade de cacheExcelenteVariável

Quando Usar Cada Um

Use Stack quando:

  • O tamanho é conhecido em tempo de compilação
  • Os dados são pequenos (< alguns KB)
  • Os dados só são necessários no escopo atual

Use Heap quando:

  • O tamanho é determinado em runtime
  • Os dados são grandes (ex: buffers de MB)
  • Os dados precisam sobreviver além do escopo
  • Você precisa de estruturas que crescem (ArrayList, HashMap)

Armadilhas Comuns

  • Stack overflow: Arrays muito grandes na stack (ex: var buffer: [10_000_000]u8) causam stack overflow. Use heap para dados grandes.
  • Dangling pointers: Retornar ponteiros para variáveis locais da stack é comportamento indefinido.
  • Memory leaks: Esquecer de liberar memória do heap com defer allocator.free().
  • Assumir tamanho da stack: O tamanho da stack varia entre plataformas. Não assuma 8MB.

Termos Relacionados

Tutoriais Relacionados

Continue aprendendo Zig

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