@memcpy em Zig — Referência e Exemplos

@memcpy em Zig

O @memcpy copia dados de um slice de origem para um slice de destino. Os slices devem ter o mesmo comprimento e não devem se sobrepor na memória. É a forma idiomática de copiar blocos de memória em Zig, frequentemente otimizada para instruções SIMD pela CPU.

Sintaxe

@memcpy(dest: []T, source: []const T) void

Parâmetros

  • dest ([]T): Slice de destino (mutável).
  • source ([]const T): Slice de origem (pode ser const).

Os dois slices devem ter exatamente o mesmo comprimento. Se tiverem comprimentos diferentes, é erro de compilação ou panic em runtime.

Valor de retorno

void — a operação modifica a memória de destino diretamente.

Exemplos práticos

Exemplo 1: Copiar entre arrays

const std = @import("std");

pub fn main() void {
    const origem = [_]u8{ 'Z', 'i', 'g' };
    var destino: [3]u8 = undefined;

    @memcpy(&destino, &origem);

    std.debug.print("Copiado: {s}\n", .{&destino}); // "Zig"
}

Exemplo 2: Copiar parte de um slice

const std = @import("std");

pub fn main() void {
    const dados = "Hello, Zig Brasil!";
    var buffer: [3]u8 = undefined;

    // Copiar "Zig" (posições 7..10)
    @memcpy(&buffer, dados[7..10]);

    std.debug.print("Extraído: {s}\n", .{&buffer}); // "Zig"
}

Exemplo 3: Duplicar slice com allocator

const std = @import("std");

fn duplicar(allocator: std.mem.Allocator, original: []const u8) ![]u8 {
    const copia = try allocator.alloc(u8, original.len);
    @memcpy(copia, original);
    return copia;
}

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

    const original = "Zig Brasil";
    const copia = try duplicar(gpa.allocator(), original);
    defer gpa.allocator().free(copia);

    std.debug.print("Original: {s}\n", .{original});
    std.debug.print("Cópia: {s}\n", .{copia});
}

Casos de uso comuns

  1. Duplicar slices: Criar cópias independentes de dados.
  2. Construir strings: Montar strings copiando partes de várias fontes.
  3. Serialização: Copiar structs para buffers de bytes.
  4. Buffers de rede: Copiar dados entre buffers de envio/recebimento.

Regras importantes

  • Os slices não devem se sobrepor na memória. Para cópias sobrepostas, use std.mem.copyBackwards ou std.mem.copyForwards.
  • Os slices devem ter o mesmo comprimento. Use fatiamento ([0..n]) para ajustar.

Comparação com C equivalente

Em C, memcpy recebe um tamanho em bytes e ponteiros sem tipo:

#include <string.h>
void* src = ...;
void* dst = ...;
memcpy(dst, src, tamanho_em_bytes); // sem verificação de tipos ou tamanhos

Em Zig, @memcpy trabalha com slices tipados e o compilador verifica comprimentos:

@memcpy(destino, origem); // destino e origem devem ter o mesmo comprimento e tipo

A versão Zig é mais segura pois:

  • O tipo dos elementos é verificado em compilação (não se pode copiar bytes de um []i32 para um []f64 sem cast explícito).
  • O comprimento é verificado em runtime (modo debug) ou compilação quando possível.
  • Não é necessário calcular tamanho * sizeof(elemento) manualmente.

Desempenho e otimizações

O @memcpy é mapeado para operações de cópia de memória altamente otimizadas. O compilador pode:

  • Usar instruções SIMD (SSE, AVX, NEON) para copiar múltiplos bytes por instrução.
  • Aplicar loop unrolling para tamanhos conhecidos em comptime.
  • Usar rep movsb em x86 para cópias grandes.
  • Inlinar a cópia completamente para tamanhos pequenos.

Para tamanhos pequenos conhecidos em compilação (ex: copiar uma struct de 16 bytes), o compilador geralmente gera 2-3 instruções de load/store sem nenhum loop.

Cópia de structs via slice de bytes

@memcpy pode ser usado para cópia bitwise de structs, tratando-as como bytes:

const std = @import("std");

const Ponto = struct { x: f32, y: f32, z: f32 };

fn clonarPonto(p: Ponto) Ponto {
    var resultado: Ponto = undefined;
    const src = std.mem.asBytes(&p);
    const dst = std.mem.asBytes(&resultado);
    @memcpy(dst, src);
    return resultado;
}

No entanto, para structs simples, a atribuição direta (resultado = p) é mais idiomática e produz o mesmo código.

Perguntas Frequentes

P: O que acontece se os slices se sobrepuserem?

Em modo debug, isso pode ser detectado e causar um panic (comportamento ilegal detectado). Em modo release, o resultado é undefined behavior — a memória pode ficar corrompida silenciosamente. Use std.mem.copyForwards ou std.mem.copyBackwards para cópias sobrepostas.

P: Posso usar @memcpy para copiar slices de tipos não-u8?

Sim. @memcpy funciona com slices de qualquer tipo, não apenas u8. Por exemplo, @memcpy(slice_i32_dst, slice_i32_src) copia arrays de inteiros diretamente.

P: @memcpy é mais rápido que um loop manual for?

Geralmente sim. O compilador otimiza @memcpy com instruções de cópia de memória em bloco (SIMD, rep movs, etc.) que são significativamente mais eficientes que loops for elemento a elemento, especialmente para tamanhos grandes.

Builtins relacionados

  • @memset — Preenche memória com um valor
  • @sizeOf — Tamanho de tipos em bytes
  • @ptrCast — Converter ponteiros antes de copiar

Tutoriais relacionados

Continue aprendendo Zig

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