@memset em Zig — Referência e Exemplos

@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

  1. Inicialização de buffers: Preencher buffers com zero ou valor padrão antes do uso.
  2. Limpeza de dados sensíveis: Zerar memória que continha senhas ou chaves criptográficas.
  3. Criação de separadores: Preencher arrays de caracteres para formatação visual.
  4. 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 stosb em 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

Tutoriais relacionados

Continue aprendendo Zig

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