std.mem.Allocator — Interface de Alocação
A interface Allocator é o mecanismo central de alocação de memória em Zig. Em vez de usar uma função global como malloc, o Zig requer que alocadores sejam passados explicitamente, tornando a gestão de memória transparente e testável.
Visão Geral
Todo código que precisa alocar memória dinâmica recebe um Allocator como parâmetro. Isso permite:
- Transparência: Fica claro quais funções alocam memória
- Testabilidade: Testes podem usar alocadores que detectam vazamentos
- Flexibilidade: O chamador escolhe a estratégia de alocação
const std = @import("std");
const Allocator = std.mem.Allocator;
A Interface
pub const Allocator = struct {
ptr: *anyopaque,
vtable: *const VTable,
pub const VTable = struct {
alloc: *const fn (ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8,
resize: *const fn (ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ret_addr: usize) ?[*]u8,
free: *const fn (ctx: *anyopaque, buf: []u8, buf_align: u8, ret_addr: usize) void,
};
};
Métodos Principais
Alocação
// Aloca um slice de N elementos do tipo T
pub fn alloc(self: Allocator, comptime T: type, n: usize) Allocator.Error![]T
// Aloca e inicializa com zero
pub fn allocWithOptions(self: Allocator, comptime T: type, n: usize, comptime alignment: ?u29, comptime sentinel: ?T) ![]T
// Cria uma única instância na heap
pub fn create(self: Allocator, comptime T: type) Allocator.Error!*T
Liberação
// Libera memória alocada
pub fn free(self: Allocator, memory: anytype) void
// Destrói instância criada com create
pub fn destroy(self: Allocator, ptr: anytype) void
Redimensionamento
// Tenta redimensionar sem realocar
pub fn resize(self: Allocator, old_mem: anytype, new_n: usize) ?[]T
// Realoca (pode mover os dados)
pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) ![]T
Duplicação
// Duplica um slice
pub fn dupe(self: Allocator, comptime T: type, m: []const T) ![]T
// Duplica string com sentinela null
pub fn dupeZ(self: Allocator, comptime T: type, m: []const T) ![:0]T
Exemplo 1: Uso Básico de Alocador
const std = @import("std");
pub fn main() !void {
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();
// Alocar array dinâmico
const numeros = try allocator.alloc(u32, 10);
defer allocator.free(numeros);
for (numeros, 0..) |*n, i| {
n.* = @intCast(i * i);
}
std.debug.print("Quadrados: ", .{});
for (numeros) |n| {
std.debug.print("{d} ", .{n});
}
std.debug.print("\n", .{});
// Criar instância na heap
const Ponto = struct { x: f64, y: f64 };
const p = try allocator.create(Ponto);
defer allocator.destroy(p);
p.* = .{ .x = 3.14, .y = 2.71 };
std.debug.print("Ponto: ({d:.2}, {d:.2})\n", .{ p.x, p.y });
}
Exemplo 2: Função que Recebe Allocator
const std = @import("std");
const Allocator = std.mem.Allocator;
const Pessoa = struct {
nome: []const u8,
idade: u32,
allocator: Allocator,
fn init(allocator: Allocator, nome: []const u8, idade: u32) !Pessoa {
return .{
.nome = try allocator.dupe(u8, nome),
.idade = idade,
.allocator = allocator,
};
}
fn deinit(self: *Pessoa) void {
self.allocator.free(self.nome);
}
fn apresentar(self: Pessoa) void {
std.debug.print("Olá, sou {s} e tenho {d} anos.\n", .{ self.nome, self.idade });
}
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var pessoa = try Pessoa.init(allocator, "Maria", 30);
defer pessoa.deinit();
pessoa.apresentar();
}
Exemplo 3: Construindo String Dinâmica
const std = @import("std");
fn construirSaudacao(allocator: std.mem.Allocator, nomes: []const []const u8) ![]u8 {
var lista = std.ArrayList(u8).init(allocator);
defer lista.deinit();
const writer = lista.writer();
try writer.writeAll("Olá");
for (nomes, 0..) |nome, i| {
if (i == nomes.len - 1 and nomes.len > 1) {
try writer.writeAll(" e ");
} else if (i > 0) {
try writer.writeAll(", ");
} else {
try writer.writeAll(" ");
}
try writer.writeAll(nome);
}
try writer.writeAll("!");
return try lista.toOwnedSlice();
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const nomes = [_][]const u8{ "Ana", "Bruno", "Carla" };
const msg = try construirSaudacao(allocator, &nomes);
defer allocator.free(msg);
std.debug.print("{s}\n", .{msg}); // "Olá Ana, Bruno e Carla!"
}
Padrões Comuns
Padrão init/deinit
Tipos que alocam memória seguem o padrão init/deinit:
const MeuTipo = struct {
dados: []u8,
allocator: Allocator,
pub fn init(allocator: Allocator) !MeuTipo {
return .{
.dados = try allocator.alloc(u8, 1024),
.allocator = allocator,
};
}
pub fn deinit(self: *MeuTipo) void {
self.allocator.free(self.dados);
}
};
Escolhendo o Alocador
// Uso geral com detecção de vazamentos
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();
// Arena para alocações temporárias em grupo
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const alloc_arena = arena.allocator();
// Buffer fixo sem syscalls
var buf: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf);
const alloc_fba = fba.allocator();
Módulos Relacionados
- std.mem — Visão geral de operações de memória
- std.heap — Implementações de alocadores
- GeneralPurposeAllocator — Alocador de uso geral
- ArenaAllocator — Alocador de arena
- std.ArrayList — Lista dinâmica que usa Allocator