wandres.dev
INGENIERO · Memoria dinámica

Memoria dinámica: el heap

malloc, calloc, realloc y free. Cómo pedir memoria en tiempo de ejecución, los patrones correctos, y los cuatro errores clásicos que causan crashes y vulnerabilidades.

⏱ 16 min

Hasta ahora tu memoria ha vivido en el stack, gestionada por el compilador. Pero cuando no sabes el tamaño de antemano, o los datos deben sobrevivir a la función que los crea, necesitas el heap — y con él, la responsabilidad de gestionarlo tú. Aquí está el poder y el peligro de C.

🎯 Al terminar esta lección sabrás
  • Stack vs heap: cuándo cada uno.
  • malloc, calloc, realloc y free.
  • El patrón correcto de reserva y liberación.
  • Los cuatro errores clásicos de memoria.

Stack vs heap

📚

Stack

Automático, rapidísimo, se libera solo al salir de la función. Tamaño limitado y fijo en compilación. Variables locales.

🗄️

Heap

Manual: tú pides y tú liberas. Tamaño decidido en ejecución, vive hasta que lo liberas. Para datos grandes o de vida larga.

malloc y free

malloc(n) reserva n bytes y devuelve un puntero (o nullptr si falla). free los devuelve:

#include <stdlib.h>

int *v = malloc(100 * sizeof(int));   // espacio para 100 ints
if (v == nullptr) { /* manejar el fallo */ return -1; }

for (int i = 0; i < 100; i++) v[i] = i;

free(v);        // devolver la memoria
v = nullptr;    // evitar un puntero colgante
💡
El idioma sizeof correcto

Escribe malloc(n * sizeof *v) en vez de malloc(n * sizeof(int)): usar sizeof *v (el tamaño de lo que apunta v) hace que el código siga siendo correcto aunque cambies el tipo de v, y elimina una fuente de bugs. Y comprueba siempre el retorno: malloc puede fallar, y dereferenciar su nullptr es un crash.

calloc y realloc

int *z = calloc(100, sizeof *z);      // como malloc pero pone todo a CERO
int *m = realloc(v, 200 * sizeof *v); // redimensiona un bloque existente
if (m) v = m;                          // realloc puede mover el bloque

calloc inicializa a cero (útil y detecta desbordamientos de multiplicación). realloc crece o encoge un bloque, posiblemente moviéndolo — por eso asignas su resultado a una variable temporal antes de pisar el original (si realloc falla y hicieras v = realloc(v,...), perderías v y su memoria).

Los cuatro pecados de la memoria

🛑
Los errores que definen la reputación de C

Cuatro errores causan la mayoría de los crashes y vulnerabilidades de C. Conócelos por su nombre:

1. Memory leak (fuga): reservas y nunca liberas. La memoria se acumula hasta agotarse. malloc sin su free.

2. Double free: liberas dos veces el mismo puntero. Corrompe las estructuras internas del asignador.

3. Use-after-free (puntero colgante): usas memoria ya liberada. El bug más explotado en seguridad hoy.

4. Buffer overflow: escribes más allá de lo reservado. Corrompe memoria adyacente.

Ninguno da un error inmediato y claro: fallan más tarde, en otro sitio, de forma no determinista. Por eso son tan difíciles de depurar — y por eso existen los sanitizers (nivel 24) y Valgrind (nivel 25), que los cazan en el acto.

La disciplina que te salva

Cada malloc tiene un dueño y un free

La gestión manual de memoria no es intratable: es una disciplina. Las reglas que siguen los programadores de sistemas serios: (1) cada malloc tiene exactamente un free correspondiente; decide desde el inicio quién es el “dueño” de cada bloque y quién lo libera. (2) Tras free, pon el puntero a nullptr — un double free o use-after-free sobre nullptr crashea limpio en vez de corromper. (3) En funciones con varios recursos, usa el patrón goto cleanup (nivel 6) para liberar en orden al fallar. (4) Empareja visualmente cada reserva con su liberación al escribir el código, no “después”. Con esta disciplina, más los sanitizers como red, la memoria manual deja de dar miedo y se vuelve predecible. El nivel 14 lleva estas ideas a estrategias de asignación que escalan.

⚔️ Gestiona el heap sin fugas
  1. Reserva un array dinámico con malloc(n * sizeof *v), úsalo y libéralo.
  2. Comprueba el retorno de malloc y maneja el fallo.
  3. Redimensiona un bloque con realloc usando una variable temporal.
  4. Provoca a propósito un leak, un double free y un use-after-free, y córrelos bajo AddressSanitizer (-fsanitize=address) para ver cómo los caza.