@memset em Zig
O @memset preenche uma região de memória (slice) com um valor específico. É a forma idiomática em Zig de inicializar buffers, zerar memória ou preencher arrays com um valor padrão. Internamente, pode ser otimizado para instruções de memória da CPU.
Sintaxe
@memset(dest: []T, value: T) void
Parâmetros
- dest (
[]T): Slice de destino a ser preenchido. - value (
T): Valor com o qual preencher cada posição do slice.
Valor de retorno
void — a operação modifica a memória diretamente.
Exemplos práticos
Exemplo 1: Inicializar buffer com zeros
const std = @import("std");
pub fn main() void {
var buffer: [1024]u8 = undefined;
// Preencher com zeros
@memset(&buffer, 0);
std.debug.print("Primeiros bytes: {any}\n", .{buffer[0..8]});
// { 0, 0, 0, 0, 0, 0, 0, 0 }
}
Exemplo 2: Preencher com caractere específico
const std = @import("std");
pub fn main() void {
var linha: [40]u8 = undefined;
// Preencher com traços para criar separador visual
@memset(&linha, '-');
std.debug.print("{s}\n", .{&linha});
// ----------------------------------------
}
Exemplo 3: Limpar dados sensíveis
const std = @import("std");
fn processarSenha(senha: []const u8) void {
var copia: [256]u8 = undefined;
const tamanho = @min(senha.len, copia.len);
@memcpy(copia[0..tamanho], senha[0..tamanho]);
// Processar a senha...
std.debug.print("Senha tem {} chars\n", .{tamanho});
// Limpar a cópia da memória antes de sair do escopo
@memset(copia[0..tamanho], 0);
}
pub fn main() void {
processarSenha("minha-senha-secreta");
}
Casos de uso comuns
- Inicialização de buffers: Preencher buffers com zero ou valor padrão antes do uso.
- Limpeza de dados sensíveis: Zerar memória que continha senhas ou chaves criptográficas.
- Criação de separadores: Preencher arrays de caracteres para formatação visual.
- Preparação de memória alocada: Inicializar memória retornada por allocators.
Comparação com C equivalente
Em C, memset recebe um int para o valor (internamente tratado como unsigned char) e um tamanho em bytes:
#include <string.h>
char buffer[1024];
memset(buffer, 0, sizeof(buffer)); // zerar buffer
memset(buffer, '-', sizeof(buffer)); // preencher com traços
Em Zig, @memset é tipado e usa o tipo do elemento diretamente:
var buffer: [1024]u8 = undefined;
@memset(&buffer, 0); // zerar
@memset(&buffer, '-'); // preencher com traços
A principal diferença: em C, memset(ptr, valor, n) preenche com o byte valor repetidamente — funciona bem para u8, mas é incorreto para tipos maiores como u32 ou f64. Em Zig, @memset preenche cada elemento com o valor do tipo correto:
var ints: [4]u32 = undefined;
@memset(&ints, 1); // cada u32 recebe o valor 1, não 0x01010101
Alternativa: inicialização com undefined e valores padrão
Para structs e tipos complexos, a sintaxe de inicialização de Zig pode ser mais clara que @memset:
// Usando @memset com valor zero
var buffer: [100]u8 = undefined;
@memset(&buffer, 0);
// Alternativa idiomática em Zig — inicialização direta
var buffer: [100]u8 = [_]u8{0} ** 100;
// Para structs, std.mem.zeroes é mais expressivo
const std = @import("std");
var config: MinhaStruct = std.mem.zeroes(MinhaStruct);
Use @memset quando o slice já existe e precisa ser reinicializado, ou quando o valor de preenchimento é determinado em runtime.
Desempenho
@memset é compilado para instruções otimizadas de preenchimento de memória. O compilador pode usar:
rep stosbem x86 para preenchimento de bytes.- Instruções SIMD (
vpbroadcastb,vmovdqu) para preenchimento de blocos grandes. - Loop unrolling para tamanhos conhecidos em comptime.
Para arrays de bytes ([]u8), o desempenho é equivalente ao memset da libc. Para tipos maiores, o Zig pode gerar código mais eficiente do que chamadas repetidas ao memset de C.
Limpeza segura de dados sensíveis
Ao limpar senhas e chaves criptográficas, existe um problema: compiladores agressivos podem remover @memset se detectarem que a memória não é mais usada após a chamada (dead store elimination). Para garantir que a limpeza ocorra, use std.crypto.utils.secureZero:
const std = @import("std");
fn processarChave(chave: []const u8) void {
var copia: [32]u8 = undefined;
@memcpy(copia[0..chave.len], chave);
// Processar...
// @memset pode ser otimizado para fora pelo compilador!
// Use secureZero para garantir a limpeza:
std.crypto.utils.secureZero(u8, &copia);
}
Perguntas Frequentes
P: @memset funciona com tipos não-u8, como []i32 ou []f64?
Sim. @memset funciona com slices de qualquer tipo. Cada elemento do slice recebe o valor especificado. Por exemplo, @memset(slice_f64, 0.0) preenche cada f64 com zero de ponto flutuante, não simplesmente com bytes zero (embora para 0.0 o resultado seja o mesmo).
P: Como preencher apenas parte de um slice?
Use fatiamento para especificar o intervalo: @memset(buffer[10..20], 0) preenche apenas as posições 10 a 19 com zero.
P: Qual a diferença entre @memset(buf, 0) e std.mem.zeroes?
@memset reinicializa um slice já existente. std.mem.zeroes(T) cria e retorna um novo valor do tipo T com todos os bytes zerados. Use zeroes para inicialização e @memset para reinicialização de buffers existentes.
Builtins relacionados
- @memcpy — Copia bloco de memória
- @sizeOf — Calcular tamanho do buffer
- @ptrCast — Converter ponteiros antes de memset