Segmentation Fault — Como Resolver em Zig

Segmentation Fault — Como Resolver em Zig

O Que Este Erro Significa

O Segmentation Fault (segfault ou SIGSEGV) ocorre quando o programa tenta acessar uma região de memória que não lhe pertence ou que não está mapeada. O sistema operacional detecta o acesso ilegal e encerra o processo com um sinal SIGSEGV. Em Zig, apesar do sistema de segurança em builds Debug, segfaults ainda podem ocorrer em determinadas situações, especialmente quando se usa ponteiros brutos, código @cImport-ado ou builds ReleaseFast.

A mensagem típica é:

Segmentation fault at address 0x0

Ou o programa simplesmente é encerrado pelo sistema operacional.

Causas Comuns

1. Desreferenciar Ponteiro Null

pub fn main() void {
    var ptr: ?*u32 = null;
    // Converte optional para ponteiro bruto (bypass de segurança)
    const raw: *u32 = @ptrFromInt(0);
    _ = raw.*; // SEGFAULT: endereço 0x0
    _ = ptr;
}

2. Uso de Ponteiro Após Free (Dangling Pointer)

const std = @import("std");

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

    const dados = try allocator.alloc(u8, 100);
    allocator.free(dados);

    // SEGFAULT: memória já foi liberada
    dados[0] = 42;
}

3. Acesso a Memória Não Inicializada

pub fn main() void {
    var buffer: [100]u8 = undefined;
    // Em ReleaseFast, undefined pode ser qualquer valor
    const ptr: *u32 = @ptrCast(@alignCast(&buffer));
    _ = ptr.*;
    // Pode causar SEGFAULT dependendo do conteúdo de buffer
}

4. Interação com Código C Inseguro

const c = @cImport({
    @cInclude("stdlib.h");
    @cInclude("string.h");
});

pub fn main() void {
    const ptr = c.malloc(100);
    c.free(ptr);
    // SEGFAULT: uso após free via API C
    _ = c.memset(ptr, 0, 100);
}

5. Stack Overflow Que Se Manifesta como Segfault

fn recursaoInfinita() void {
    recursaoInfinita(); // Estoura a stack
    // Pode aparecer como segfault ao invés de stack overflow
}

pub fn main() void {
    recursaoInfinita();
}

Como Corrigir

Solucao 1: Sempre Verificar Optionals Antes de Usar

const std = @import("std");

pub fn main() void {
    var ptr: ?*u32 = null;

    if (ptr) |p| {
        std.debug.print("valor: {}\n", .{p.*});
    } else {
        std.debug.print("Ponteiro é null\n", .{});
    }
}

Solucao 2: Evitar Ponteiros Após Free

const std = @import("std");

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

    const dados = try allocator.alloc(u8, 100);
    defer allocator.free(dados); // defer garante free no momento certo

    // Usa dados enquanto são válidos
    dados[0] = 42;
    dados[99] = 255;
    // free acontece aqui automaticamente via defer
}

Solucao 3: Inicializar Memória Explicitamente

const std = @import("std");

pub fn main() void {
    // Use valor concreto ao invés de undefined
    var buffer = std.mem.zeroes([100]u8);
    // Ou use @memset
    @memset(&buffer, 0);

    // Agora é seguro usar buffer
    buffer[0] = 42;
}

Solucao 4: Usar Slices ao Invés de Ponteiros Brutos

const std = @import("std");

fn processar(dados: []u8) void {
    // Slices têm verificação de limites em Debug
    for (dados) |*byte| {
        byte.* = 0;
    }
}

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

    const buffer = try allocator.alloc(u8, 100);
    defer allocator.free(buffer);

    processar(buffer);
}

Solucao 5: Compilar em Modo Debug para Diagnóstico

Compile com modo Debug (padrão) para obter mensagens de erro melhores:

zig build        # Modo Debug por padrão
zig build-exe main.zig  # Também Debug por padrão

Em modo Debug, muitos segfaults são transformados em panics com stack trace legível:

thread 1 panic: index out of bounds
/home/user/main.zig:12:15: 0x20401a in main

Solucao 6: Wrapping Seguro de Código C

const c = @cImport({
    @cInclude("stdlib.h");
});

fn alocarSeguro(tamanho: usize) !*anyopaque {
    const ptr = c.malloc(tamanho);
    if (ptr == null) return error.OutOfMemory;
    return ptr.?;
}

fn liberarSeguro(ptr: *anyopaque) void {
    c.free(ptr);
}

Ferramentas de Diagnóstico

Usando address sanitizer (ASan)

Zig pode compilar com sanitizers para detectar problemas de memória:

zig build-exe -fsanitize=address main.zig

Usando Valgrind

zig build-exe main.zig
valgrind ./main

Stack Trace em Debug

Em builds Debug, Zig fornece stack traces detalhados. Sempre desenvolva em modo Debug primeiro e só mude para ReleaseFast/ReleaseSafe quando necessário.

Diferenças entre Modos de Build

ModoVerificações de SegurançaSegfault Detectado Como
DebugTodasPanic com stack trace
ReleaseSafeTodasPanic com stack trace
ReleaseFastNenhumaSegfault bruto
ReleaseSmallNenhumaSegfault bruto

Erros Relacionados

Continue aprendendo Zig

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