¿Qué es un zig memory layout?

¿Qué es un zig memory layout?

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. u32 ocupa 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 llamada Punto con dos campos de tipo i32.
  • 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 b necesita alineación 4, así que el compilador insertará 3 bytes de relleno entre a y b.
  • El tamaño total será 8 bytes: 1 byte de a, 3 bytes de padding, 4 bytes de b.

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 packed fuerza 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});
}

Comments

No comments yet. Why don’t you start the discussion?

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *