← Volver al listado de tecnologías

Punteros y Memoria en Zig

Por: Artiko
zigpunterosmemoriaallocators

Punteros y Memoria

Tipos de Punteros

const std = @import("std");

pub fn main() void {
    var valor: i32 = 42;

    // Puntero simple
    const ptr: *i32 = &valor;
    std.debug.print("Valor: {d}\n", .{ptr.*});

    // Puntero constante
    const ptr_const: *const i32 = &valor;
    // ptr_const.* = 10; // Error: no se puede modificar

    // Modificar a través del puntero
    ptr.* = 100;
    std.debug.print("Nuevo valor: {d}\n", .{valor});

    // Puntero a muchos elementos
    var arr = [_]i32{ 1, 2, 3, 4, 5 };
    const many_ptr: [*]i32 = &arr;
    std.debug.print("Tercer elemento: {d}\n", .{many_ptr[2]});
}

Punteros Opcionales

const std = @import("std");

fn buscarPuntero(arr: []i32, valor: i32) ?*i32 {
    for (arr) |*elem| {
        if (elem.* == valor) return elem;
    }
    return null;
}

pub fn main() void {
    var numeros = [_]i32{ 10, 20, 30, 40 };

    if (buscarPuntero(&numeros, 30)) |ptr| {
        std.debug.print("Encontrado: {d}\n", .{ptr.*});
        ptr.* = 300; // Modificar el valor original
    }

    std.debug.print("Array modificado: {any}\n", .{numeros});
}

Allocators

Zig no tiene garbage collector. La memoria se gestiona con allocators:

const std = @import("std");

pub fn main() !void {
    // Page allocator (del OS)
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Asignar memoria para un valor
    const ptr = try allocator.create(i32);
    defer allocator.destroy(ptr);

    ptr.* = 42;
    std.debug.print("Valor: {d}\n", .{ptr.*});

    // Asignar array dinámico
    const arr = try allocator.alloc(u8, 100);
    defer allocator.free(arr);

    @memset(arr, 0);
    std.debug.print("Array de {d} bytes creado\n", .{arr.len});
}

Tipos de Allocators

const std = @import("std");

pub fn main() !void {
    // 1. General Purpose Allocator (recomendado para desarrollo)
    var gpa = std.heap.GeneralPurposeAllocator(.{
        .safety = true,
        .stack_trace_size = 8,
    }){};
    defer {
        const check = gpa.deinit();
        if (check == .leak) {
            std.debug.print("Memory leak detectado!\n", .{});
        }
    }

    // 2. Fixed Buffer Allocator (sin syscalls)
    var buffer: [1024]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);
    const fba_alloc = fba.allocator();

    const datos = try fba_alloc.alloc(u8, 100);
    _ = datos;

    // 3. Arena Allocator (libera todo al final)
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const arena_alloc = arena.allocator();

    // No necesitas free individual, arena.deinit() libera todo
    _ = try arena_alloc.alloc(u8, 500);
    _ = try arena_alloc.alloc(u8, 500);
}

ArrayList Dinámico

const std = @import("std");

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

    // Crear ArrayList
    var lista = std.ArrayList(i32).init(allocator);
    defer lista.deinit();

    // Agregar elementos
    try lista.append(10);
    try lista.append(20);
    try lista.append(30);

    // Agregar múltiples
    try lista.appendSlice(&[_]i32{ 40, 50 });

    // Iterar
    for (lista.items) |item| {
        std.debug.print("{d} ", .{item});
    }

    // Acceso por índice
    std.debug.print("\nPrimero: {d}\n", .{lista.items[0]});

    // Eliminar último
    _ = lista.pop();
    std.debug.print("Longitud: {d}\n", .{lista.items.len});
}

HashMap

const std = @import("std");

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

    // HashMap string -> i32
    var mapa = std.StringHashMap(i32).init(allocator);
    defer mapa.deinit();

    // Insertar
    try mapa.put("uno", 1);
    try mapa.put("dos", 2);
    try mapa.put("tres", 3);

    // Buscar
    if (mapa.get("dos")) |valor| {
        std.debug.print("dos = {d}\n", .{valor});
    }

    // Iterar
    var iter = mapa.iterator();
    while (iter.next()) |entry| {
        std.debug.print("{s}: {d}\n", .{ entry.key_ptr.*, entry.value_ptr.* });
    }

    // Eliminar
    _ = mapa.remove("uno");
}

Defer y Errdefer

const std = @import("std");

fn abrirRecursos(allocator: std.mem.Allocator) ![]u8 {
    const buffer1 = try allocator.alloc(u8, 100);
    errdefer allocator.free(buffer1); // Solo si hay error después

    const buffer2 = try allocator.alloc(u8, 100);
    errdefer allocator.free(buffer2);

    // Si esta línea falla, buffer1 y buffer2 se liberan
    if (buffer1.len + buffer2.len > 150) {
        return error.MuyGrande;
    }

    allocator.free(buffer2);
    return buffer1;
}

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

    const datos = try abrirRecursos(gpa.allocator());
    defer gpa.allocator().free(datos);

    std.debug.print("Recursos abiertos: {d} bytes\n", .{datos.len});
}

Testing de Memoria

const std = @import("std");
const testing = std.testing;

fn duplicarArray(allocator: std.mem.Allocator, arr: []const i32) ![]i32 {
    const nuevo = try allocator.alloc(i32, arr.len);
    @memcpy(nuevo, arr);
    return nuevo;
}

fn crearLista(allocator: std.mem.Allocator) !std.ArrayList(i32) {
    var lista = std.ArrayList(i32).init(allocator);
    try lista.append(1);
    try lista.append(2);
    try lista.append(3);
    return lista;
}

// Test allocator detecta memory leaks
test "duplicar array sin leaks" {
    const arr = [_]i32{ 1, 2, 3, 4, 5 };
    const copia = try duplicarArray(testing.allocator, &arr);
    defer testing.allocator.free(copia);

    try testing.expectEqual(@as(usize, 5), copia.len);
    try testing.expectEqual(@as(i32, 1), copia[0]);
    try testing.expectEqual(@as(i32, 5), copia[4]);
}

test "ArrayList con test allocator" {
    var lista = try crearLista(testing.allocator);
    defer lista.deinit();

    try testing.expectEqual(@as(usize, 3), lista.items.len);
    try testing.expectEqual(@as(i32, 1), lista.items[0]);
}

test "HashMap básico" {
    var mapa = std.StringHashMap(i32).init(testing.allocator);
    defer mapa.deinit();

    try mapa.put("a", 1);
    try mapa.put("b", 2);

    try testing.expectEqual(@as(?i32, 1), mapa.get("a"));
    try testing.expectEqual(@as(?i32, 2), mapa.get("b"));
    try testing.expectEqual(@as(?i32, null), mapa.get("c"));
}

test "puntero opcional" {
    var valor: i32 = 42;
    var ptr: ?*i32 = &valor;

    try testing.expect(ptr != null);
    try testing.expectEqual(@as(i32, 42), ptr.?.*);

    ptr = null;
    try testing.expect(ptr == null);
}

test "fixed buffer allocator" {
    var buffer: [256]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);
    const allocator = fba.allocator();

    const arr = try allocator.alloc(u8, 100);
    try testing.expectEqual(@as(usize, 100), arr.len);

    // Verificar que no podemos asignar más de lo disponible
    const resultado = allocator.alloc(u8, 200);
    try testing.expectError(error.OutOfMemory, resultado);
}

Ejercicios

  1. Implementa una función que concatene dos slices usando un allocator
  2. Crea un struct Stack genérico con push/pop usando ArrayList
  3. Implementa un cache simple con HashMap que limite el número de entradas

Resumen