stack overflow — Como Resolver em Zig

stack overflow — Como Resolver em Zig

O Que Este Erro Significa

O erro stack overflow ocorre quando a pilha de execução (stack) do programa excede seu limite de tamanho. Cada chamada de função em Zig aloca espaço na pilha para variáveis locais, parâmetros e endereço de retorno. Quando esse espaço se esgota — geralmente por recursão sem condição de parada ou por alocação de estruturas muito grandes na pilha — o sistema operacional sinaliza um erro de estouro de pilha.

A mensagem pode aparecer como:

thread 1 panic: reached stack guard page

Ou simplesmente um segfault em sistemas que não detectam o estouro graciosamente.

Causas Comuns

1. Recursão Infinita (Sem Caso Base)

fn contar(n: u32) u32 {
    return contar(n + 1); // STACK OVERFLOW: nunca para
}

pub fn main() void {
    _ = contar(0);
}

2. Caso Base Incorreto na Recursão

fn fatorial(n: u64) u64 {
    if (n == 0) return 1;
    return n * fatorial(n); // BUG: deveria ser fatorial(n - 1)
    // n nunca muda, recursão infinita
}

3. Recursão Mútua Sem Término

fn funcA(n: u32) u32 {
    return funcB(n);
}

fn funcB(n: u32) u32 {
    return funcA(n); // Loop infinito entre funcA e funcB
}

4. Array Grande Demais na Stack

pub fn main() void {
    var buffer: [100_000_000]u8 = undefined; // 100 MB na stack!
    // STACK OVERFLOW: a stack padrão é ~8 MB
    _ = buffer;
}

5. Struct Grande Passada por Valor

const DadosGrandes = struct {
    buffer: [10_000_000]u8, // 10 MB
};

fn processar(dados: DadosGrandes) void { // Copia na stack!
    _ = dados;
}

Como Corrigir

Solução 1: Adicionar Caso Base Correto na Recursão

fn fatorial(n: u64) u64 {
    if (n <= 1) return 1; // Caso base correto
    return n * fatorial(n - 1); // Progride em direção ao caso base
}

Solução 2: Converter Recursão em Iteração

fn fatorial(n: u64) u64 {
    var resultado: u64 = 1;
    var i: u64 = 2;
    while (i <= n) : (i += 1) {
        resultado *= i;
    }
    return resultado;
}

Solução 3: Limitar Profundidade de Recursão

const std = @import("std");

fn buscar(dados: []const u8, profundidade: u32) ?usize {
    if (profundidade > 1000) {
        std.debug.print("Profundidade máxima alcançada\n", .{});
        return null;
    }
    // ... lógica de busca recursiva ...
    return buscar(dados, profundidade + 1);
}

Solução 4: Alocar Dados Grandes no Heap

const std = @import("std");

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

    // Aloca no heap ao invés da stack
    const buffer = try allocator.alloc(u8, 100_000_000);
    defer allocator.free(buffer);

    // Usa buffer normalmente
    buffer[0] = 42;
}

Solução 5: Passar Structs Grandes por Ponteiro

const DadosGrandes = struct {
    buffer: [10_000_000]u8,
};

fn processar(dados: *const DadosGrandes) void { // Ponteiro: apenas 8 bytes na stack
    _ = dados;
}

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

    const dados = try allocator.create(DadosGrandes);
    defer allocator.destroy(dados);
    processar(dados);
}

Solução 6: Aumentar o Tamanho da Stack (Thread)

Em casos legítimos de recursão profunda:

const std = @import("std");

pub fn main() void {
    const thread = std.Thread.spawn(.{
        .stack_size = 64 * 1024 * 1024, // 64 MB de stack
    }, funcaoRecursiva, .{}) catch @panic("falha ao criar thread");
    thread.join();
}

fn funcaoRecursiva() void {
    // Função que precisa de stack grande
}

Tamanho Padrão da Stack

O tamanho padrão da stack varia por plataforma:

PlataformaTamanho Padrão
Linux8 MB
macOS8 MB
Windows1 MB

Para threads criadas com std.Thread, o padrão pode ser menor (geralmente 2 MB em Linux).

Diagnóstico

Identificar Recursão Infinita

Use @returnAddress ou trace para ver se a mesma função aparece repetidamente na pilha:

const std = @import("std");

fn funcaoSuspeita(n: u32) u32 {
    std.debug.print("chamada com n={}\n", .{n});
    if (n == 0) return 0;
    return funcaoSuspeita(n - 1);
}

Verificar Tamanho de Variáveis Locais

fn exemplo() void {
    // Verifique o tamanho antes de alocar na stack
    @compileLog(@sizeOf([1_000_000]u8)); // 1 MB — pode ser demais
}

Erros Relacionados

Continue aprendendo Zig

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