En Zig, la forma en que los datos se colocan en la memoria es un concepto fundamental que determina cómo el compilador genera código eficiente y cómo los programadores controlan el comportamiento de sus programas a nivel de bajo nivel. Cuando hablamos de “memory layout” en Zig nos referimos a la disposición física de los bytes que representan tipos de datos en la memoria, incluyendo el orden de los campos en estructuras, el alineamiento requerido por cada tipo, el tamaño de los tipos primitivos y cómo se manejan los punteros, matrices y slices.
Comprendiendo el Layout de Memoria
En la mayoría de los lenguajes de alto nivel, la gestión de memoria es automática y el desarrollador no necesita preocuparse por la disposición exacta de los datos. Zig, por otro lado, está diseñado para la programación de sistemas donde el control explícito de la memoria es esencial. Por eso, Zig ofrece herramientas a nivel de compilador para inspeccionar y modificar el layout de memoria.
Tipos Primarios y Sus Atributos
- int, uint: Tamaño y alineación según especificación (p.ej.
u32ocupa 4 bytes y requiere alineación 4). - float: Igual que los enteros, con tamaños típicos de 32 o 64 bits.
- bool: 1 byte, alineado a 1.
- char, u8: 1 byte.
- ptr: Tamaño depende de la arquitectura (4 bytes en 32‑bit, 8 bytes en 64‑bit).
El compilador Zig ofrece las funciones std.meta.sizeOf(Type) y std.meta.alignOf(Type) para obtener, respectivamente, el tamaño en bytes y el alineamiento requerido de cualquier tipo.
Estructuras: Orden y Padding
Las estructuras (struct) en Zig siguen un orden de declaración de campos y añaden relleno (padding) cuando es necesario para cumplir con los requisitos de alineación de cada campo. Esta política garantiza que cada campo esté accesible de forma eficiente por la CPU.
// Ejemplo 1: Estructura simple con padding automático
const std = @import("std");
const Punto = struct {
x: i32, // 4 bytes, alineación 4
y: i32, // 4 bytes, alineación 4
};
pub fn main() void {
const punto = Punto{ .x = 10, .y = 20 };
// Mostrar el tamaño total y alineamiento
const tsize = std.meta.sizeOf(Punto);
const talign = std.meta.alignOf(Punto);
std.debug.print("Punto: size = {d} bytes, align = {d} bytes\n", .{tsize, talign});
}
**Explicación línea por línea**
const std = @import("std");– Importamos el estándar de Zig para poder usar funciones de depuración.const Punto = struct { ... };– Declaramos una estructura llamadaPuntocon dos campos de tipoi32.const punto = Punto{ .x = 10, .y = 20 };– Instanciamos la estructura con valores iniciales.const tsize = std.meta.sizeOf(Punto);– Calculamos el tamaño total de la estructura (debería ser 8 bytes).const talign = std.meta.alignOf(Punto);– Calculamos el alineamiento requerido (debería ser 4 bytes).std.debug.print(...);– Imprimimos la información de depuración.
Ahora veamos un caso donde el relleno entra en juego.
// Ejemplo 2: Relleno automático debido a alineamiento
const std = @import("std");
const Mezcla = struct {
a: u8, // 1 byte, alineación 1
b: u32, // 4 bytes, alineación 4
};
pub fn main() void {
const mezcla = Mezcla{ .a = 1, .b = 2 };
const tsize = std.meta.sizeOf(Mezcla);
const talign = std.meta.alignOf(Mezcla);
std.debug.print("Mezcla: size = {d} bytes, align = {d} bytes\n", .{tsize, talign});
}
- El campo
bnecesita alineación 4, así que el compilador insertará 3 bytes de relleno entreayb. - El tamaño total será 8 bytes: 1 byte de
a, 3 bytes de padding, 4 bytes deb.
Control de Padding con align y packed
Si quieres evitar relleno (por ejemplo, para protocolos de red que exigen un formato compacto) puedes usar la etiqueta packed. No obstante, debes tener cuidado porque puede provocar accesos no alineados y penalizaciones de rendimiento en CPUs modernas.
// Ejemplo 3: Estructura empaquetada
const std = @import("std");
const Packed = packed struct {
a: u8,
b: u32,
};
pub fn main() void {
const packed = Packed{ .a = 1, .b = 2 };
const tsize = std.meta.sizeOf(Packed);
const talign = std.meta.alignOf(Packed);
std.debug.print("Packed: size = {d} bytes, align = {d} bytes\n", .{tsize, talign});
}
- Ahora el tamaño será 5 bytes (1 + 4) sin relleno.
- El alineamiento será 1, pues
packedfuerza la alineación mínima.
Matrices y Slices
Las matrices ([]const T) en Zig son contenedores de longitud fija. Cada elemento ocupa el tamaño de su tipo, y la matriz completa es contigua en memoria. Los slices, por otro lado, son vistas sobre una porción de memoria y consisten en un puntero y una longitud.
// Ejemplo 4: Matriz y slice
const std = @import("std");
pub fn main() void {
// Matriz de 3 enteros
const arr = [3]u32{ 10, 20, 30 };
// Slice que apunta a la matriz completa
const slice = arr[0..];
std.debug.print("Matriz size: {d} bytes\n", .{std.meta.sizeOf(@TypeOf(arr))});
std.debug.print("Slice pointer: {any}\n", .{slice.ptr});
std.debug.print("Slice length: {d}\n", .{slice.len});
}
const arr = [3]u32{ 10, 20, 30 };– Crea una matriz
