Introdução
Uma das maiores diferenças entre C e Zig é o gerenciamento de memória. Em C, malloc e free são funções globais — qualquer código pode alocar memória de qualquer lugar. Em Zig, toda alocação é feita através de um allocator passado explicitamente como parâmetro.
Este guia mostra como substituir os padrões de malloc/free por allocators Zig. Para detalhes sobre cada allocator, consulte ArenaAllocator, GeneralPurposeAllocator e FixedBufferAllocator.
Pré-requisitos
- Zig instalado (versão 0.13+). Veja Como Instalar Zig
- Conhecimento básico de C e alocação de memória
- Familiaridade com Zig. Consulte Introdução ao Zig
O Problema com malloc/free
Em C, alocação de memória é invisível e global:
// Quem libera? Quando? Nenhuma informação no tipo.
char* ler_arquivo(const char* caminho) {
FILE* f = fopen(caminho, "r");
// ...
char* conteudo = malloc(tamanho);
fread(conteudo, 1, tamanho, f);
fclose(f);
return conteudo; // O chamador precisa saber que deve chamar free()
}
Problemas comuns:
- Double free: Liberar a mesma memória duas vezes
- Use-after-free: Usar memória já liberada
- Memory leak: Esquecer de liberar
- Invisibilidade: Não é claro quais funções alocam
- Não testável: Não dá para substituir o alocador em testes
A Solução: Allocators Explícitos
Em Zig, o allocator é um parâmetro explícito:
fn lerArquivo(allocator: std.mem.Allocator, caminho: []const u8) ![]u8 {
const arquivo = try std.fs.cwd().openFile(caminho, .{});
defer arquivo.close();
return arquivo.readToEndAlloc(allocator, 1024 * 1024);
}
// O chamador SABE que memória foi alocada e é responsável por liberar:
const conteudo = try lerArquivo(allocator, "dados.txt");
defer allocator.free(conteudo);
Mapeamento malloc/free para Zig
Alocação Simples
// C
int* ptr = (int*)malloc(sizeof(int));
*ptr = 42;
free(ptr);
// Zig
const ptr = try allocator.create(i32);
defer allocator.destroy(ptr);
ptr.* = 42;
Alocação de Array
// C
int* arr = (int*)malloc(100 * sizeof(int));
memset(arr, 0, 100 * sizeof(int));
free(arr);
// Zig
const arr = try allocator.alloc(i32, 100);
defer allocator.free(arr);
@memset(arr, 0);
Realloc
// C
int* arr = malloc(10 * sizeof(int));
arr = realloc(arr, 20 * sizeof(int));
free(arr);
// Zig
var arr = try allocator.alloc(i32, 10);
arr = try allocator.realloc(arr, 20);
defer allocator.free(arr);
Strings Dinâmicas
// C
char* msg = malloc(256);
snprintf(msg, 256, "Olá, %s!", nome);
free(msg);
// Zig
const msg = try std.fmt.allocPrint(allocator, "Olá, {s}!", .{nome});
defer allocator.free(msg);
Escolhendo o Allocator Certo
GeneralPurposeAllocator (GPA)
Equivalente mais próximo ao malloc, com detecção de bugs em debug:
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const status = gpa.deinit();
if (status == .leak) {
std.debug.print("Vazamento de memória detectado!\n", .{});
}
}
const allocator = gpa.allocator();
Use para: aplicações de uso geral, desenvolvimento, detecção de bugs.
ArenaAllocator
Aloca tudo e libera de uma vez. Ideal para processamento em fases:
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit(); // Libera TUDO de uma vez
const allocator = arena.allocator();
// Alocar livremente sem se preocupar com free individual
const a = try allocator.alloc(u8, 100);
const b = try allocator.alloc(u8, 200);
const c = try allocator.alloc(u8, 300);
// Tudo liberado no arena.deinit()
Use para: parsing, processamento de requisições, operações com lifecycle definido. Veja ArenaAllocator.
FixedBufferAllocator
Aloca de um buffer fixo na stack, sem heap:
var buffer: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
const dados = try allocator.alloc(u8, 100);
// Sem heap allocation — tudo na stack
Use para: sistemas embarcados, hot paths, quando heap não é desejável. Veja FixedBufferAllocator.
page_allocator
Aloca diretamente do sistema operacional em páginas:
const allocator = std.heap.page_allocator;
const dados = try allocator.alloc(u8, 4096);
defer allocator.free(dados);
Use para: alocações grandes, backing allocator para arenas.
Padrão de Migração
Passo 1: Identificar Funções que Alocam
Em C, busque por malloc, calloc, realloc, strdup, e qualquer função que retorne ponteiros heap-alocados.
Passo 2: Adicionar Allocator como Parâmetro
// Antes (tradução direta de C)
fn processar() ![]u8 {
return std.heap.c_allocator.alloc(u8, 100); // RUIM: alocador hardcoded
}
// Depois (idiomático Zig)
fn processar(allocator: std.mem.Allocator) ![]u8 {
return allocator.alloc(u8, 100); // BOM: alocador injetado
}
Passo 3: Usar defer/errdefer
fn criarRecurso(allocator: std.mem.Allocator) !*Recurso {
const r = try allocator.create(Recurso);
errdefer allocator.destroy(r); // Libera se o resto falhar
r.* = .{
.dados = try allocator.alloc(u8, 1024),
.estado = .inicializando,
};
errdefer allocator.free(r.dados);
try r.inicializar();
return r;
}
Veja Padrões Errdefer.
Passo 4: Testar com Allocators de Teste
test "processar não vaza memória" {
// testing.allocator detecta leaks automaticamente
const resultado = try processar(std.testing.allocator);
defer std.testing.allocator.free(resultado);
try std.testing.expect(resultado.len == 100);
}
Veja Testes com Allocator e Detectar Vazamentos.
Padrões Avançados
Arena por Requisição
fn processarRequisicao(req: Requisicao) !Resposta {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const alloc = arena.allocator();
// Todas as alocações desta requisição usam a arena
const parsed = try parsearBody(alloc, req.body);
const resultado = try executarLogica(alloc, parsed);
return criarResposta(resultado);
}
Struct com Allocator
const MinhaStruct = struct {
allocator: std.mem.Allocator,
dados: []u8,
pub fn init(allocator: std.mem.Allocator) !MinhaStruct {
return .{
.allocator = allocator,
.dados = try allocator.alloc(u8, 256),
};
}
pub fn deinit(self: *MinhaStruct) void {
self.allocator.free(self.dados);
}
};
Conclusão
Substituir malloc/free por allocators Zig melhora significativamente a segurança e testabilidade do código. O allocator como parâmetro explícito torna cada alocação visível e permite trocar estratégias de memória sem mudar a lógica do programa.
Para mais sobre gerenciamento de memória, veja Gerenciamento de Memória em Zig. Para migração de projeto, consulte Guia de Migração: C para Zig.