Menu

malloc en C: memoria dinámica, free y sizeof

Por qué existe el heap, cómo malloc te entrega memoria cuyo tamaño se decide en tiempo de ejecución, el modismo p = malloc(n * sizeof *p), comprobar NULL, liberar exactamente una vez, y un array dinámico resuelto de principio a fin.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Todos los arrays que has escrito hasta ahora tenían su tamaño fijado en tiempo de compilación: int scores[10]; reserva diez espacios sin importar si necesitas tres o trescientos. Eso funciona hasta que el tamaño solo se conoce mientras el programa corre: cuántas líneas tiene un archivo, cuántos registros ingresa el usuario. La memoria dinámica resuelve eso: pides los bytes que necesitas, cuando los necesitas, y los devuelves cuando terminas.

Las herramientas están en <stdlib.h>:

#include <stdlib.h>

Por qué existe el heap

C le da a tu programa dos regiones principales de memoria. Las variables locales viven en el almacenamiento automático (la pila): el compilador decide su tamaño y desaparecen en el momento en que su función retorna. Las reservas dinámicas viven en el heap: tú decides el tamaño en tiempo de ejecución, y el bloque sigue vivo hasta que lo liberas explícitamente.

Esa segunda propiedad es la verdadera razón para usar malloc. Una función puede reservar un bloque, devolver el puntero, y la memoria sigue siendo válida en quien la llamó: algo que un array local nunca puede hacer. Las dos regiones se comparan en detalle en pila frente a heap.

malloc: pedir bytes

malloc toma una cantidad de bytes y devuelve un void * apuntando a esa cantidad de memoria sin inicializar, o NULL si la petición falla.

Cuatro cosas en ese programa corto son toda la disciplina:

  1. La expresión de tamaño es n * sizeof *scores. Léela como "n de lo que sea que scores apunte".
  2. El resultado se comprueba contra NULL antes de cualquier uso.
  3. El bloque se usa exactamente como un array: scores[i] funciona porque indexar es aritmética de punteros.
  4. Se libera una vez, y el puntero se pone entonces a NULL.

Fíjate que no hay conversión sobre el valor de retorno. En C, void * se convierte automáticamente a cualquier puntero a objeto, y escribir (int *)malloc(...) agrega ruido mientras oculta un <stdlib.h> que falta. (C++ exige la conversión; C no.)

El modismo de sizeof

¿Por qué sizeof *scores en lugar de sizeof(int)? Porque no puede quedarse obsoleto.

int  *a = malloc(n * sizeof *a);     /* n ints, sea lo que sea int aquí */
long *b = malloc(n * sizeof *b);     /* la misma forma de línea, el tamaño correcto */

/* la forma frágil */
long *c = malloc(n * sizeof(int));   /* compila, reserva demasiado poco */

La última línea es un error real del que ningún compilador advierte: c es un long *, la reserva está dimensionada para int, y cada escritura más allá de la primera mitad se sale del final. Con sizeof *c el tamaño sigue a la declaración automáticamente.

sizeof *p no desreferencia p: sizeof se evalúa en tiempo de compilación solo a partir del tipo, así que es seguro incluso cuando p está sin inicializar o es NULL.

Memoria sin inicializar

malloc no limpia lo que te da. Los bytes contienen lo que hubiera antes.

Cuando quieras el bloque puesto a cero por ti, calloc lo hace en un solo paso: mira calloc y realloc.

free: devolverlo

free(p) devuelve el bloque al asignador. Tres reglas:

  • Pasa el puntero exacto que malloc devolvió, no uno que haya sido avanzado. free(p + 1) es comportamiento indefinido.
  • Libera cada bloque exactamente una vez. Dos veces es una doble liberación, que corrompe la contabilidad interna del asignador.
  • Después de liberar, el puntero está obsoleto. Usarlo es un uso después de liberar.
int *p = malloc(sizeof *p);
free(p);
*p = 5;        /* uso después de liberar - comportamiento indefinido */
free(p);       /* doble liberación - comportamiento indefinido */

Ninguna de las dos líneas está obligada a fallar de inmediato, que es lo que las hace peligrosas: el programa puede correr por minutos y luego fallar en algún lugar sin relación. La defensa barata es blanquear el puntero:

free(p);
p = NULL;

free(NULL) está explícitamente definido para no hacer nada, así que un segundo free(p) después de eso es inofensivo, y *p se convierte en un fallo inmediato en el sitio del error real en lugar de una corrupción silenciosa.

Un array dinámico, de principio a fin

Esta es la forma de una reserva real: leer un conteo en tiempo de ejecución, dimensionar el bloque a partir de él, usarlo, liberarlo.

La conversión a size_t sobre el conteo importa una vez que los tamaños crecen: n * sizeof *values con un n de tipo int simple puede desbordarse antes de que la multiplicación llegue siquiera a malloc, produciendo un bloque mucho más pequeño de lo previsto. Multiplicar en size_t evita eso.

Reservar dentro de una función

La regla de tiempo de vida del heap es lo que hace esto legal, y lo que hace ilegal devolver un array local:

len + 1 deja espacio para el terminador nulo: la misma regla de dimensionado que en todas partes en cadenas. Y fíjate en el comentario encima de la función: cuando una función devuelve memoria reservada, quién la libera es parte de su contrato. Escribirlo no es burocracia; es lo único que evita una fuga.

Structs en el heap

El mismo modismo funciona para cualquier tipo:

Fíjate que la segunda rama de fallo libera p antes de retornar. Una vez que una función tiene más de una reserva, cada camino de error tiene que liberar las que ya tomó: la fuente más común de las fugas descritas en fugas de memoria.

Lista de verificación

  • Comprueba siempre malloc contra NULL antes del primer uso.
  • Dimensiona con n * sizeof *p, multiplicando en size_t.
  • Sin conversión sobre el valor de retorno en C.
  • La memoria de malloc está sin inicializar; escribe antes de leer.
  • Libera exactamente una vez, con el puntero original, y luego ponlo a NULL.
  • Cada camino de error después de una reserva debe liberar lo que ya tiene.

Preguntas frecuentes

¿Qué hace malloc en C?

malloc(n) pide n bytes de memoria del heap y devuelve un puntero al inicio de ese bloque, o NULL si la petición no se puede satisfacer. El bloque vive hasta que le pasas ese mismo puntero a free: a diferencia de una variable local, no se libera cuando la función retorna.

¿Cómo se usan malloc y free en C?

Reserva con int *p = malloc(n * sizeof *p);, comprueba if (p == NULL) antes de tocarlo, úsalo como un array, y luego free(p); exactamente una vez cuando termines. Poner p = NULL después convierte un uso accidental posterior en un fallo limpio en lugar de una corrupción silenciosa.

¿Por qué malloc(n * sizeof *p) es mejor que malloc(n * sizeof(int))?

Porque sizeof *p sigue automáticamente el tipo del puntero. Si p más tarde se convierte en un long * o un struct Point *, el tamaño de la reserva se actualiza solo; escribir sizeof(int) deja un tamaño que ahora está mal y que el compilador no señalará.

¿Qué pasa si no liberas la memoria en C?

El bloque sigue reservado durante toda la vida del proceso: una fuga de memoria. Un programa corto se sale con la suya porque el sistema operativo recupera todo al salir, pero un programa de larga duración fuga un poco en cada pasada y termina agotando la memoria. Mira fugas de memoria.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR