Cuando programamos en C, a menudo nos enfrentamos al reto de manejar arrays cuyo tamaño no conocemos en tiempo de compilación. Por ejemplo, si queremos crear un programa que calcule estadísticas basándose en datos que el usuario introducirá, no podemos saber de antemano cuántos elementos habrá. Para estos casos, C99 introdujo los arrays de longitud variable, conocidos como VLA, que permiten definir un array cuyo tamaño se determina en tiempo de ejecución mediante una variable.
Podemos declarar un array de temperaturas con un tamaño variable usando una variable, digamos i, que podría valer 20, 30 o cualquier otro valor que obtengamos dinámicamente. Sin embargo, los VLAs tienen limitaciones importantes. Primero, solo están disponibles a partir de C99, por lo que si trabajamos en entornos que requieren C89 o versiones anteriores, no podremos usarlos. Además, los VLAs se almacenan en la pila, una zona de memoria limitada. Si intentamos reservar un array demasiado grande, corremos el riesgo de que el programa falle por falta de espacio en la pila.
Para superar estas limitaciones, recurrimos a la memoria dinámica, una técnica que está disponible en todas las versiones de C, incluida la ANSI. La memoria dinámica nos permite reservar espacio en el heap, una zona de memoria mucho más amplia que la pila, ideal para manejar grandes cantidades de datos o tamaños variables.
Para trabajar con memoria dinámica, primero debemos incluir la cabecera stdlib.h, que contiene las funciones necesarias. La función principal para reservar memoria es malloc, que recibe como parámetro el número de bytes que queremos reservar y devuelve un puntero a esa zona de memoria. Por ejemplo, si queremos reservar espacio para 4000 elementos de tipo float, no basta con pasar 4000 a malloc, porque cada float ocupa más de un byte (normalmente 4 bytes). Por eso, multiplicamos el número de elementos por el tamaño del tipo usando sizeof(float).
El resultado de malloc es un puntero de tipo void*, que debemos convertir (hacer un cast) al tipo de puntero que vamos a usar, en este caso float*. Así, podemos almacenar el resultado en una variable como float *valores.
Veamos un ejemplo práctico donde preguntamos al usuario cuántos elementos quiere almacenar:
#include <stdio.h>
#include <stdlib.h>
int main() {
int longitud;
printf("¿Cuántos elementos quieres almacenar? ");
scanf("%d", &longitud);
float *valores = (float *)malloc(longitud * sizeof(float));
if (valores == NULL) {
printf("No tienes tanta memoria disponible.\n");
return 1;
}
for (int i = 0; i < longitud; i++) {
valores[i] = 5.55e5; // Asignamos un valor de ejemplo
}
printf("Todo está correcto.\n");
free(valores);
return 0;
}
En este código, primero pedimos al usuario la cantidad de elementos, luego reservamos memoria para ese número de float. Es fundamental comprobar que malloc no devuelva NULL, lo que indicaría que la reserva de memoria ha fallado, por ejemplo, porque el sistema no tiene suficiente memoria disponible. Si esto ocurre, podemos manejar el error mostrando un mensaje y terminando el programa o intentando otras estrategias.
Una diferencia clave entre los VLAs y la memoria dinámica es la gestión de la vida útil de la memoria. Los VLAs son variables locales que desaparecen automáticamente cuando salimos de la función donde se declararon. En cambio, la memoria reservada con malloc permanece asignada hasta que la liberamos explícitamente con free. Si olvidamos liberar esta memoria, nuestro programa puede acabar consumiendo cada vez más memoria, lo que se conoce como una fuga de memoria.
Para liberar la memoria, simplemente llamamos a free pasando el puntero que apunta a la zona reservada. Después de llamar a free, no debemos usar ese puntero ni acceder a la memoria que apuntaba, porque ya no es válida y puede provocar errores o fallos en el programa.
Así, la gestión correcta de la memoria dinámica implica reservar con malloc, usar la memoria según nuestras necesidades, y finalmente liberar con free para mantener el programa eficiente y evitar problemas.
Para profundizar más en C y sus características, es recomendable consultar referencias clásicas como el libro de Kernighan y Ritchie, que es una fuente fundamental para entender el lenguaje en profundidad.