@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
- Duplicar slices: Criar cópias independentes de dados.
- Construir strings: Montar strings copiando partes de várias fontes.
- Serialização: Copiar structs para buffers de bytes.
- 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.copyBackwardsoustd.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
[]i32para um[]f64sem 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 movsbem 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