¿Cómo usar std.testing.allocator para fugas?

¿Cómo usar std.testing.allocator para fugas?

Para los principiantes que están dando sus primeros pasos con Zig, entender cómo gestionar la memoria es crucial. El módulo std.testing ofrece una herramienta muy útil llamada allocator que nos permite detectar fugas de memoria de manera sencilla y sin complicaciones. A continuación, explicaremos paso a paso cómo usarlo y veremos ejemplos prácticos con comentarios detallados.

Entendiendo std.testing.allocator

En Zig, la gestión de memoria se realiza a través de allocators, que son objetos que controlan cómo se reserva y libera memoria. Cuando escribes pruebas unitarias, es frecuente que olvides liberar alguna zona de memoria, lo que resulta en fugas que pueden pasar desapercibidas en entornos de desarrollo pero provocar problemas en producción.

El campo allocator dentro del módulo std.testing es un allocator especial que registra todas las asignaciones realizadas durante la ejecución de las pruebas. Si al final de la prueba alguna de esas asignaciones no ha sido liberada, std.testing.allocator detecta la fuga y reporta un error.

¿Cómo funciona internamente?

  • Cuando se llama a allocator.alloc() se almacena la dirección y el tamaño en una tabla interna.
  • Al liberar memoria con allocator.free() se elimina la entrada correspondiente.
  • Al finalizar la prueba, el allocator revisa la tabla; si quedan entradas, genera un error indicando que se produjo una fuga.

Detectar fugas en pruebas unitarias

Configuración básica

Para aprovechar std.testing.allocator basta con incluir el módulo y usar su instancia en el test. Aquí tienes un ejemplo minimalista:

const std = @import("std");

// El allocator especial para pruebas
const testingAllocator = &std.testing.allocator;

test "ejemplo sin fuga" {
    // Reservamos 100 bytes
    const ptr = testingAllocator.alloc(u8, 100) catch unreachable;

    // Hacemos algo con la memoria (ejemplo: rellenar con ceros)
    @memset(ptr, 0);

    // Liberamos la memoria
    testingAllocator.free(ptr);
}

En este caso la prueba pasa sin problemas porque la memoria se libera correctamente. Si comentamos la línea que libera la memoria, la prueba fallará:

// testingAllocator.free(ptr); // descomentar para generar la fuga

Al ejecutar el test, Zig mostrará un mensaje indicando una fuga de memoria, lo que nos ayuda a corregir el error.

Ejemplo con función que devuelve una cadena

Supongamos que queremos crear una función que asigna memoria para una cadena y la devuelve al llamador. Queremos asegurarnos de que el llamador libere la memoria después de usarla. El siguiente código muestra cómo estructurarlo y cómo verificar la liberación:

const std = @import("std");
const testingAllocator = &std.testing.allocator;

/// Crea una cadena duplicada
fn duplicateString(alloc: *std.mem.Allocator, src: []const u8) ![]u8 {
    // Asignar memoria suficiente para la cadena y el terminador nulo
    const dst = try alloc.alloc(u8, src.len + 1);

    // Copiar contenido
    @memcpy(dst[0..src.len], src);

    // Añadir terminador nulo
    dst[src.len] = 0;

    return dst;
}

test "duplicar string sin fuga" {
    // Usamos el allocator de pruebas
    const str = try duplicateString(testingAllocator, "hola");

    // Hacemos algo con la cadena
    std.debug.print("Cadena duplicada: {s}\n", .{str});

    // Liberamos la memoria al final de la prueba
    testingAllocator.free(str);
}

Si olvidamos la línea testingAllocator.free(str);, la prueba fallará y Zig nos informará de la fuga, lo que nos permite detectar rápidamente el problema.

Buenas prácticas para evitar fugas de memoria en Zig

  • Usa siempre defer cuando sea posible. El bloque defer garantiza que la memoria se liberará al salir del ámbito, incluso si ocurre un error.
  • Comprueba el resultado de todas las asignaciones. Cada llamada a alloc() devuelve un error!; no ignores el resultado.
  • Prueba exhaustivamente con std.testing.allocator. Integra el allocator en cada test para detectar fugas en tiempo de desarrollo.
  • Revisa el código antes de compilar. Los compiladores modernos de Zig pueden emitir advertencias si detectan asignaciones que no se liberan.

Ejemplo con defer

test "ejemplo con defer" {
    const ptr = testingAllocator.alloc(u8, 50) catch unreachable;

    // Garantizamos que se libere al final de la prueba
    defer testingAllocator.free(ptr);

    // Operaciones con ptr
    @memset(ptr, 42);
}

El uso de defer hace que la prueba sea más robusta y menos propensa a errores de olvidos.

Resumen y próximos pasos

El std.testing.allocator es una herramienta poderosa y sencilla para detectar fugas de memoria en Zig durante el desarrollo de pruebas unitarias. Al combinarlo con buenas prácticas de programación y el uso de defer, puedes escribir código más seguro y confiable.

Para seguir profundizando:

  1. Explora los detalles de la API de memoria de Zig.
  2. Implementa tests que incluyan varios tipos de asignaciones, como alloc(), allocStruct() y allocSlice().
  3. Integra el uso de std.testing.allocator en tus proyectos de producción para monitorear el uso de memoria en tiempo de ejecución.

Con estos conceptos y ejemplos, ya tienes una base sólida para gestionar memoria y detectar fugas en tus proyectos Zig. ¡A seguir programando!

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 *