En el mundo de Zig, la gestión de la memoria es un punto central que garantiza tanto el rendimiento como la seguridad del programa. Una de las rutinas más útiles para manipular bloques de memoria de manera eficiente es std.mem.copyBackwards, que permite copiar datos de una zona de origen a una zona de destino procesando los bytes de derecha a izquierda. Esta característica resulta esencial cuando se trabaja con buffers que pueden solaparse, evitando la sobreescritura inesperada que ocurriría si se copiara de izquierda a derecha.
Fundamentos de std.mem.copyBackwards
La firma de la función es la siguiente:
pub fn copyBackwards(dest: []u8, src: []const u8) void
destes un slice mutable donde se almacenará el resultado.srces un slice constante que contiene los datos que se van a copiar.- La función asume que
dest.len == src.leny no realiza verificaciones de tamaño; por ello, es responsabilidad del programador garantizar la precondición.
El algoritmo subyacente recorre los slices desde el último índice hasta el primero, copiando cada byte individualmente. Este enfoque evita que el contenido sobreescriba los datos de origen antes de que hayan sido leídos.
Uso básico con slices
Imaginemos que queremos copiar un bloque de datos de un buffer a otro del mismo tamaño sin solapamiento. A continuación, se muestra un ejemplo completo con comentarios línea por línea:
const std = @import("std");
pub fn main() !void {
// Creamos un allocator de prueba (el global allocator de Zig)
const allocator = std.heap.page_allocator;
// Definimos el tamaño de los buffers
const size = 10;
// Asignamos memoria para el buffer de origen
var src = try allocator.alloc(u8, size);
defer allocator.free(src);
// Asignamos memoria para el buffer de destino
var dest = try allocator.alloc(u8, size);
defer allocator.free(dest);
// Inicializamos el origen con valores 0..9
for (src) |*elem, i| {
elem.* = @intCast(u8, i);
}
// Copiamos de src a dest usando copyBackwards
std.mem.copyBackwards(dest, src);
// Mostramos el contenido de dest
std.debug.print("Buffer destino: {any}\n", .{dest});
}
Salida esperada:
Buffer destino: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
En este caso, el resultado es idéntico al original porque no hay solapamiento.
Manejo de zonas de memoria superpuestas
Cuando los slices de origen y destino comparten parte de la misma memoria, copiar de izquierda a derecha puede sobreescribir datos que aún no se han leído. copyBackwards resuelve este problema al trabajar en orden inverso.
const std = @import("std");
pub fn main() !void {
// Creamos un buffer de 20 bytes
var buffer = [_]u8{0} ** 20;
// Llenamos los primeros 10 bytes con valores 0..9
for (buffer[0..10]) |*elem, i| {
elem.* = @intCast(u8, i);
}
// Supongamos que queremos desplazar los 10 primeros bytes 5 posiciones hacia la derecha
// sin usar un buffer temporal.
// Aquí src y dest se solapan: dest empieza en buffer[5] y src termina en buffer[10]
const src = buffer[0..10];
const dest = buffer[5..15];
// Copiamos con copyBackwards para evitar que se sobrescriban los datos antes de leerlos
std.mem.copyBackwards(dest, src);
// Imprimimos el buffer completo para observar el resultado
std.debug.print("Buffer completo: {any}\n", .{buffer});
}
Salida esperada:
Buffer completo: [0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0]
Observa cómo los primeros cinco bytes del buffer quedan preservados mientras los datos se desplazan sin corrupción.
- Revisa siempre que
src.len == dest.lenantes de llamar acopyBackwards. - Evita que
destysrcestén completamente distintos si quieres un comportamiento definido. - Usa
copyBackwardssolo cuando la sobreposición sea una posibilidad; de lo contrario,std.mem.copyes más rápido.
Ejemplo avanzado: mover estructuras entre buffers
Supongamos que manejamos una lista de estructuras de datos que necesitamos reorganizar sin copiar manualmente cada campo. El siguiente ejemplo demuestra cómo copiar un array de estructuras de manera segura, incluso cuando el destino está parcialmente dentro del origen.
const std = @import("std");
const Person = struct {
id: u32,
name: [20]u8,
};
pub fn main() !void {
const allocator = std.heap.page_allocator;
// Creamos un buffer que contendrá 3 Person
const size = 3;
var src = try allocator.alloc(Person, size);
defer allocator.free(src);
// Inicializamos los datos
src[0] = Person{ .id = 1, .name = "Alice".* };
src[1] = Person{ .id = 2, .name = "Bob".* };
src[2] = Person{ .id = 3, .name = "Carol".* };
// Ahora creamos un buffer destino que sobrelapará parcialmente con src
// Tomamos el segundo y tercer elemento de src y los movemos al inicio
const dest = src[0..2];
// Usamos copyBackwards con slices de Person
std.mem.copyBackwards(dest, src[1..3]);
// Imprimimos el resultado
std.debug.print("Personas tras el desplazamiento:\n", .{});
for (src) |p| {
std.debug.print("id: {}, name:
