Curso de C · Módulo 8 de 12

Memoria dinámica en C: malloc, realloc y free

Qué aprenderás en este módulo

pedir memoria en tiempo de ejecución y devolverla correctamente.

Incluye 4 ejercicios con pistas y solución y un quiz de 6 preguntas. Tiempo estimado: 90 minutos.

1. Pila vs. montón

Pila (stack) Montón (heap)
Qué guarda variables locales, parámetros lo que pides con malloc
Gestión automática manual (malloc/free)
Tamaño pequeño (≈ 1-8 MB) grande (limitado por la RAM)
Dura hasta salir de la función hasta que hagas free

Un int v[1000000]; local puede reventar la pila. Y los arrays de tamaño fijo no sirven si no sabes cuántos datos habrá: ahí entra el heap.

2. Las cuatro funciones (<stdlib.h>)

int *v = malloc(n * sizeof *v);       // n enteros SIN inicializar
int *w = calloc(n, sizeof *w);        // n enteros a CERO
v = realloc(v, 2 * n * sizeof *v);    // cambia el tamaño (ver abajo: forma segura)
free(v);                              // devuelve la memoria
  • malloc recibe bytes. sizeof *v se adapta si cambias el tipo (mejor que sizeof(int)).
  • Devuelve NULL si no hay memoria → compruébalo.
  • Tras malloc, v se usa como un array normal: v[i].

Patrón de uso completo

int *v = malloc(n * sizeof *v);
if (v == NULL) { fprintf(stderr, "sin memoria\n"); return 1; }
... usar v ...
free(v);
v = NULL;           // evita usarlo "después de liberar" por accidente

realloc seguro

int *tmp = realloc(v, nuevo * sizeof *v);
if (tmp == NULL) { free(v); return 1; }   // v seguía siendo válido
v = tmp;

Si hicieras v = realloc(v, ...) y fallara, perderías el único puntero al bloque original (fuga).

Estrategia de crecimiento: duplica la capacidad cuando se llene (cap *= 2): el coste amortizado por inserción es constante.

3. Regla de oro de la propiedad

Cada malloc debe tener exactamente un free.

Decide siempre quién es dueño de la memoria. Convención clara: quien recibe el puntero de una función crear_... es quien libera.

4. Los errores clásicos

Error Consecuencia
Olvidar free fuga de memoria (el programa engorda hasta morir)
free dos veces (double free) crash/corrupción
Usar tras free (use-after-free) comportamiento indefinido
Escribir fuera del bloque (overflow) corrupción del heap
free de algo que no vino de malloc crash
Olvidar el +1 del '\0' en cadenas desbordamiento de 1 byte

5. Cadenas dinámicas

char *copia = malloc(strlen(s) + 1);   // +1 para '\0'
strcpy(copia, s);
...
free(copia);

6. Detectar errores de memoria

  • AddressSanitizer: compila con -fsanitize=address -g (gcc/clang). Detecta overflows, use-after-free y fugas.
  • Valgrind (Linux/WSL): valgrind --leak-check=full ./prog.
  • Truco casero: cuenta mallocs y frees con contadores en una función envoltorio.

Prueba esto

  1. Reserva 1000 enteros con calloc, comprueba que están a 0, y libéralos.
  2. Provoca una fuga a propósito (malloc sin free en un bucle de 1e6 iteraciones) y mira cómo sube el uso de memoria en el Administrador de tareas.
  3. Escribe char *leer_linea(void) que use realloc para crecer hasta el '\n'.

Ejercicios prácticos del módulo 8

Programar se aprende programando: intenta cada ejercicio antes de mirar las pistas, y usa la solución solo para comparar. Marca los que resuelvas y tu progreso se guarda en este navegador.

8.1 Array del tamano justo
Lee n y reserva con malloc un array de n enteros (nada de arrays de tamano fijo). Lee los n numeros e imprimelos en orden inverso, separados por espacio. No olvides comprobar que malloc no devuelve NULL y llamar a free al terminar.

Entrada de ejemplo

4
1 2 3 4

Salida esperada

4 3 2 1

Tu punto de partida

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n;
    scanf("%d", &n);
    // TODO: int *v = malloc(...)

    return 0;
}

Cópialo en tu editor, complétalo, compílalo (gcc -Wall -Wextra programa.c -o programa) y prueba la entrada de ejemplo. Para que se corrija solo con varios casos de prueba, descarga el curso.

Pista 1

malloc recibe BYTES: n * sizeof(int) (o mejor n * sizeof *v).

Pista 2

Despues de reservar, v se usa exactamente igual que un array: v[i].

Ver solución (inténtalo antes)
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n;
    scanf("%d", &n);
    int *v = malloc(n * sizeof *v);
    if (v == NULL) return 1;
    for (int i = 0; i < n; i++) scanf("%d", &v[i]);
    for (int i = n - 1; i >= 0; i--) printf("%d ", v[i]);
    printf("\n");
    free(v);
    return 0;
}
8.2 Una funcion que devuelve memoria
Implementa int *crear_rango(int a, int b) que reserve y devuelva un array con los enteros de a a b (ambos incluidos, a <= b). El main lo imprime y es quien hace el free (el que recibe, libera).

Entrada de ejemplo

3 7

Salida esperada

3 4 5 6 7

Tu punto de partida

#include <stdio.h>
#include <stdlib.h>

int *crear_rango(int a, int b) {
    // TODO
    return NULL;
}

int main(void) {
    int a, b;
    scanf("%d %d", &a, &b);
    int *r = crear_rango(a, b);
    for (int i = 0; i <= b - a; i++) printf("%d ", r[i]);
    printf("\n");
    free(r);
    return 0;
}

Cópialo en tu editor, complétalo, compílalo (gcc -Wall -Wextra programa.c -o programa) y prueba la entrada de ejemplo. Para que se corrija solo con varios casos de prueba, descarga el curso.

Pista 1

Numero de elementos: b - a + 1.

Pista 2

Aqui SI puedes devolver el puntero: la memoria del heap sobrevive a la funcion (a diferencia de una local).

Ver solución (inténtalo antes)
#include <stdio.h>
#include <stdlib.h>

int *crear_rango(int a, int b) {
    int n = b - a + 1;
    int *r = malloc(n * sizeof *r);
    if (r == NULL) return NULL;
    for (int i = 0; i < n; i++) r[i] = a + i;
    return r;
}

int main(void) {
    int a, b;
    scanf("%d %d", &a, &b);
    int *r = crear_rango(a, b);
    for (int i = 0; i <= b - a; i++) printf("%d ", r[i]);
    printf("\n");
    free(r);
    return 0;
}
8.3 Cantidad desconocida: realloc
Lee enteros hasta que aparezca 0 (el 0 no cuenta). No sabes cuantos habra, asi que usa un array dinamico que crezca con realloc (duplica la capacidad cuando se llene). Al final imprime: n=5 suma=21 mayor=8

Entrada de ejemplo

3 8 1 4 5 0

Salida esperada

n=5 suma=21 mayor=8

Tu punto de partida

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int cap = 2, n = 0;
    int *v = malloc(cap * sizeof *v);
    int x;
    while (scanf("%d", &x) == 1 && x != 0) {
        // TODO: si n == cap, duplica cap y haz realloc. Despues guarda x.

    }
    // TODO: calcula suma y mayor (si n > 0) e imprime

    free(v);
    return 0;
}

Cópialo en tu editor, complétalo, compílalo (gcc -Wall -Wextra programa.c -o programa) y prueba la entrada de ejemplo. Para que se corrija solo con varios casos de prueba, descarga el curso.

Pista 1

realloc(ptr, nuevos_bytes) devuelve el nuevo puntero: asignalo a un puntero TEMPORAL primero.

Pista 2

Si realloc falla devuelve NULL y el bloque original sigue vivo: si lo asignas directo a v pierdes la memoria.

Ver solución (inténtalo antes)
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int cap = 2, n = 0;
    int *v = malloc(cap * sizeof *v);
    int x;
    while (scanf("%d", &x) == 1 && x != 0) {
        if (n == cap) {
            cap *= 2;
            int *tmp = realloc(v, cap * sizeof *v);
            if (tmp == NULL) { free(v); return 1; }
            v = tmp;
        }
        v[n++] = x;
    }
    long long suma = 0;
    int mayor = n > 0 ? v[0] : 0;
    for (int i = 0; i < n; i++) {
        suma += v[i];
        if (v[i] > mayor) mayor = v[i];
    }
    printf("n=%d suma=%lld mayor=%d\n", n, suma, mayor);
    free(v);
    return 0;
}
8.4 Cadenas dinamicas
Implementa dos funciones que devuelven cadenas NUEVAS en el heap: char *duplicar(const char *s) copia de s (como strdup) char *unir(const char *a, const char *b) a seguido de b Puedes usar strlen/strcpy/strcat. Recuerda reservar +1 byte para el '\0'. El main libera todo.

Entrada de ejemplo

hola mundo

Salida esperada

hola|holamundo

Tu punto de partida

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *duplicar(const char *s) {
    // TODO
    return NULL;
}

char *unir(const char *a, const char *b) {
    // TODO
    return NULL;
}

int main(void) {
    char a[100], b[100];
    scanf("%99s %99s", a, b);
    char *d = duplicar(a);
    char *u = unir(a, b);
    printf("%s|%s\n", d, u);
    free(d);
    free(u);
    return 0;
}

Cópialo en tu editor, complétalo, compílalo (gcc -Wall -Wextra programa.c -o programa) y prueba la entrada de ejemplo. Para que se corrija solo con varios casos de prueba, descarga el curso.

Pista 1

El tamano necesario es strlen + 1 (por el terminador).

Pista 2

unir: strlen(a) + strlen(b) + 1, luego strcpy(r, a) y strcat(r, b).

Ver solución (inténtalo antes)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *duplicar(const char *s) {
    char *r = malloc(strlen(s) + 1);
    if (r) strcpy(r, s);
    return r;
}

char *unir(const char *a, const char *b) {
    char *r = malloc(strlen(a) + strlen(b) + 1);
    if (r) {
        strcpy(r, a);
        strcat(r, b);
    }
    return r;
}

int main(void) {
    char a[100], b[100];
    scanf("%99s %99s", a, b);
    char *d = duplicar(a);
    char *u = unir(a, b);
    printf("%s|%s\n", d, u);
    free(d);
    free(u);
    return 0;
}

Quiz del módulo 8

6 preguntas para comprobar que has fijado los conceptos. Las opciones se barajan cada vez.