---
title: "Debugging de Problemas de Memória em Zig: Guia Completo"
url: "https://ziglang.com.br/tutoriais/debugging-de-problemas-de-mem%C3%B3ria-em-zig-guia-completo/"
markdown_url: "https://ziglang.com.br/tutoriais/debugging-de-problemas-de-mem%C3%B3ria-em-zig-guia-completo.MD"
description: "Aprenda a encontrar e corrigir memory leaks, use-after-free e buffer overflows em Zig. Ferramentas, técnicas e exemplos práticos de debugging."
date: "2026-02-21"
author: "Zig Brasil"
---

# Debugging de Problemas de Memória em Zig: Guia Completo

Aprenda a encontrar e corrigir memory leaks, use-after-free e buffer overflows em Zig. Ferramentas, técnicas e exemplos práticos de debugging.


Este é o artigo final da série **Masterclass de Memória em Zig**. Depois de dominar [stack e heap](/tutoriais/zig-memoria-masterclass/artigo-1-stack-heap-fundamentos/), [tipos de allocators](/tutoriais/zig-memoria-masterclass/artigo-2-allocators-tipos/), [arena allocator](/tutoriais/zig-memoria-masterclass/artigo-3-arena-allocator-pratica/) e [custom allocators](/tutoriais/zig-memoria-masterclass/artigo-4-custom-allocator/), agora vamos aprender a diagnosticar e corrigir os bugs de memória mais comuns.

## Os Tipos de Bugs de Memória

Antes de debugar, é importante reconhecer os tipos de problemas:

| Bug | Descrição | Consequência |
|-----|-----------|-------------|
| **Memory Leak** | Memória alocada nunca liberada | Uso crescente de RAM |
| **Use-After-Free** | Acesso a memória já liberada | Dados corrompidos, crash |
| **Double Free** | Liberar a mesma memória duas vezes | Corrupção do heap |
| **Buffer Overflow** | Escrever além dos limites | Corrupção de dados adjacentes |
| **Dangling Pointer** | Ponteiro para memória inválida | Comportamento indefinido |

## Ferramenta 1: GeneralPurposeAllocator como Detector

O GPA de Zig é seu melhor amigo para detectar problemas de memória. Ele detecta leaks, double-free e use-after-free automaticamente:

```zig
const std = @import("std");

fn exemploLeak(allocator: std.mem.Allocator) !void {
    const dados = try allocator.alloc(u8, 100);
    // BUG: esquecemos de liberar 'dados'!
    _ = dados;

    const outros = try allocator.alloc(u8, 200);
    allocator.free(outros); // Este foi liberado corretamente
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{
        .stack_trace_frames = 10, // Captura mais frames para debug
    }){};
    defer {
        const status = gpa.deinit();
        if (status == .leak) {
            std.debug.print("\n*** MEMORY LEAK DETECTADO ***\n", .{});
            std.debug.print("Verifique os stack traces acima para encontrar a origem.\n", .{});
        } else {
            std.debug.print("\nSem memory leaks!\n", .{});
        }
    }

    try exemploLeak(gpa.allocator());
}
```

Ao rodar, o GPA mostrará exatamente onde a memória foi alocada e não liberada, incluindo o stack trace.

## Ferramenta 2: Padrão de Verificação com Defer

O padrão mais eficaz para prevenir leaks é usar `defer` sistematicamente:

```zig
const std = @import("std");

const Conexao = struct {
    host: []u8,
    porta: u16,
    allocator: std.mem.Allocator,
    ativa: bool,

    fn conectar(allocator: std.mem.Allocator, host: []const u8, porta: u16) !Conexao {
        const host_copia = try allocator.alloc(u8, host.len);
        @memcpy(host_copia, host);

        return Conexao{
            .host = host_copia,
            .porta = porta,
            .allocator = allocator,
            .ativa = true,
        };
    }

    fn desconectar(self: *Conexao) void {
        if (self.ativa) {
            self.allocator.free(self.host);
            self.ativa = false;
            std.debug.print("Conexão fechada e memória liberada\n", .{});
        }
    }
};

fn processarComConexao(allocator: std.mem.Allocator) !void {
    var conn = try Conexao.conectar(allocator, "localhost", 5432);
    defer conn.desconectar(); // SEMPRE limpar ao sair

    // Simular trabalho que pode falhar
    const dados = try allocator.alloc(u8, 1024);
    defer allocator.free(dados);

    std.debug.print("Conectado a {s}:{d}\n", .{ conn.host, conn.porta });

    // Mesmo que algo falhe aqui, defer garante a limpeza
    for (dados, 0..) |*b, i| {
        b.* = @intCast(i % 256);
    }
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer {
        const status = gpa.deinit();
        if (status == .leak) {
            std.debug.print("LEAK detectado!\n", .{});
        } else {
            std.debug.print("Sem leaks! Padrão defer funcionou.\n", .{});
        }
    }

    try processarComConexao(gpa.allocator());
}
```

## Ferramenta 3: Valgrind com Zig

Zig compila para código nativo, então podemos usar Valgrind para análise avançada:

```bash
# Compilar em modo debug (padrão)
zig build-exe src/main.zig

# Rodar com Valgrind
valgrind --leak-check=full --show-leak-kinds=all ./main

# Para análise de cache (performance)
valgrind --tool=cachegrind ./main

# Para detectar acessos inválidos
valgrind --tool=memcheck --track-origins=yes ./main
```

Exemplo de programa para testar com Valgrind:

```zig
const std = @import("std");

pub fn main() !void {
    // Usar c_allocator para Valgrind funcionar melhor
    const allocator = std.heap.c_allocator;

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

    // Preencher dados
    for (dados, 0..) |*b, i| {
        b.* = @intCast(i);
    }

    // Alocação que "esquecemos" de liberar (para Valgrind detectar)
    const leak = try allocator.alloc(u8, 50);
    _ = leak;

    std.debug.print("Programa finalizado. Rode com valgrind para ver o leak.\n", .{});
}
```

Valgrind mostrará algo como:
```
==12345== 50 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345==    at 0x4C2ABBD: malloc (vg_replace_malloc.c:380)
==12345==    ...
```

## Ferramenta 4: Sanitizador de Endereços (ASan)

Zig suporta AddressSanitizer nativamente:

```bash
# Compilar com sanitizador
zig build-exe -fsanitize=address src/main.zig

# Rodar — erros serão reportados automaticamente
./main
```

O ASan detecta:
- Buffer overflow na heap e stack
- Use-after-free
- Use-after-return
- Memory leaks

## Padrão: Tracking Allocator para Produção

Para monitorar memória em produção, crie um allocator de tracking leve:

```zig
const std = @import("std");

const MemoryTracker = struct {
    backing: std.mem.Allocator,
    alocacoes_ativas: usize,
    bytes_ativos: usize,
    pico_bytes: usize,
    total_alocacoes: usize,

    fn init(backing: std.mem.Allocator) MemoryTracker {
        return .{
            .backing = backing,
            .alocacoes_ativas = 0,
            .bytes_ativos = 0,
            .pico_bytes = 0,
            .total_alocacoes = 0,
        };
    }

    fn allocator(self: *MemoryTracker) std.mem.Allocator {
        return .{
            .ptr = self,
            .vtable = &.{
                .alloc = alloc,
                .resize = resize,
                .free = free_fn,
            },
        };
    }

    fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8 {
        const self: *MemoryTracker = @ptrCast(@alignCast(ctx));
        const result = self.backing.rawAlloc(len, ptr_align, ret_addr);
        if (result != null) {
            self.alocacoes_ativas += 1;
            self.bytes_ativos += len;
            self.total_alocacoes += 1;
            if (self.bytes_ativos > self.pico_bytes) {
                self.pico_bytes = self.bytes_ativos;
            }
        }
        return result;
    }

    fn resize(ctx: *anyopaque, buf: [*]u8, buf_align: u8, new_len: usize, ret_addr: usize) bool {
        const self: *MemoryTracker = @ptrCast(@alignCast(ctx));
        return self.backing.rawResize(buf, buf_align, new_len, ret_addr);
    }

    fn free_fn(ctx: *anyopaque, buf: [*]u8, buf_align: u8, ret_addr: usize) void {
        const self: *MemoryTracker = @ptrCast(@alignCast(ctx));
        if (self.alocacoes_ativas > 0) {
            self.alocacoes_ativas -= 1;
        }
        self.backing.rawFree(buf, buf_align, ret_addr);
    }

    fn relatorio(self: *const MemoryTracker) void {
        std.debug.print("\n=== Relatório de Memória ===\n", .{});
        std.debug.print("Alocações ativas: {d}\n", .{self.alocacoes_ativas});
        std.debug.print("Bytes ativos: {d}\n", .{self.bytes_ativos});
        std.debug.print("Pico de uso: {d} bytes\n", .{self.pico_bytes});
        std.debug.print("Total de alocações: {d}\n", .{self.total_alocacoes});

        if (self.alocacoes_ativas > 0) {
            std.debug.print("AVISO: {d} alocações ainda ativas!\n", .{self.alocacoes_ativas});
        }
    }
};

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

    var tracker = MemoryTracker.init(gpa.allocator());
    const alloc = tracker.allocator();

    // Simular uso da aplicação
    var buffers: [5][]u8 = undefined;
    for (&buffers, 0..) |*buf, i| {
        buf.* = try alloc.alloc(u8, (i + 1) * 100);
    }

    tracker.relatorio();

    // Liberar alguns
    for (buffers[0..3]) |buf| {
        alloc.free(buf);
    }

    tracker.relatorio();

    // Liberar o restante
    for (buffers[3..]) |buf| {
        alloc.free(buf);
    }

    tracker.relatorio();
}
```

## Checklist de Debugging de Memória

Quando encontrar um problema de memória, siga esta sequência:

1. **Ative o GPA** com `stack_trace_frames` alto
2. **Verifique `defer`** — cada `alloc`/`create` tem um `defer free`/`defer destroy`?
3. **Busque ponteiros escapando** — dados alocados na stack retornados por referência
4. **Teste com Valgrind** para análise detalhada
5. **Use ASan** para buffer overflows
6. **Adicione tracking** em produção para monitorar tendências

## Erros Comuns e Soluções

### Erro 1: Ponteiro para stack escapando

```zig
// ERRADO: retorna ponteiro para dado na stack
fn perigoso() *i32 {
    var x: i32 = 42;
    return &x; // x será destruído ao sair da função!
}

// CORRETO: alocar na heap e retornar
fn seguro(allocator: std.mem.Allocator) !*i32 {
    const x = try allocator.create(i32);
    x.* = 42;
    return x; // Caller deve chamar allocator.destroy(x)
}
```

### Erro 2: Esquecer errdefer

```zig
const std = @import("std");

const Recurso = struct {
    a: []u8,
    b: []u8,

    fn init(allocator: std.mem.Allocator) !Recurso {
        const a = try allocator.alloc(u8, 100);
        // Se a alocação de 'b' falhar, 'a' vazaria!
        errdefer allocator.free(a);

        const b = try allocator.alloc(u8, 200);
        // Se chegamos aqui, ambos foram alocados com sucesso

        return Recurso{ .a = a, .b = b };
    }

    fn deinit(self: *Recurso, allocator: std.mem.Allocator) void {
        allocator.free(self.a);
        allocator.free(self.b);
    }
};

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

    var r = try Recurso.init(gpa.allocator());
    defer r.deinit(gpa.allocator());

    std.debug.print("Recurso inicializado com {d} + {d} bytes\n", .{ r.a.len, r.b.len });
}
```

O `errdefer` executa apenas se a função retornar um erro — perfeito para limpeza parcial.

## Conclusão da Série

Ao longo desta masterclass, você aprendeu:

1. **Fundamentos** — Como stack e heap funcionam em Zig
2. **Allocators** — Cada tipo e quando usar
3. **Arena Allocator** — O padrão mais poderoso para alocações em lote
4. **Custom Allocators** — Como criar os seus próprios
5. **Debugging** — Ferramentas e técnicas para encontrar bugs

Com esse conhecimento, você está equipado para escrever código Zig que é seguro, eficiente e livre de bugs de memória.

## Leitura Complementar

- [Otimização de Performance](/tutoriais/zig-performance/) — Otimize com base no que aprendeu sobre memória
- [Testes Avançados](/tutoriais/zig-testing-avancado/) — Teste sistematicamente para bugs de memória
- [Desenvolvimento Web com Zig](/tutoriais/zig-web-development/) — Aplique gerenciamento de memória em servidores
- [Builtins de Memória](/builtins/) — Referência de funções built-in
