Memoria dinámica en C — teoría con malloc, calloc, realloc y free, un vector de notas que crece (y el Segmentation fault que me enseñó dónde vive cada cosa)

Primer mes de IC2, laboratorio de los jueves. Tenía que guardar las notas de una clase y pensé: «hago una función crear_notas() que monte el vector y me lo devuelva, como en Python». La escribí en dos minutos, compilé, ejecuté… y esto:

#include <stdio.h>

/* Quería una función que "fabricara" el vector de notas y me lo devolviera */
double *crear_notas(void) {
    double notas[3] = {7.5, 4.0, 9.0};  /* vector LOCAL: vive en la pila de crear_notas */
    return notas;                       /* devuelvo su dirección... pero al salir, deja de existir */
}

int main(void) {
    double *v = crear_notas();
    printf("Vector creado. Primera nota:\n");
    printf("%.1f\n", v[0]);             /* intento leer un vector que ya no existe */
    return 0;
}

Al compilar con gcc -Wall -std=c11, el compilador ya me estaba avisando (y yo, claro, no lo leí):

gancho.c: In function 'crear_notas':
gancho.c:6:12: warning: function returns address of local variable [-Wreturn-local-addr]

Y al ejecutar:

Vector creado. Primera nota:
Segmentation fault (core dumped)

El programa imprime la primera línea y muere. ¿Por qué? Porque notas es un vector local: vive dentro de crear_notas y desaparece en cuanto la función hace return. Le estaba devolviendo a main la dirección de algo que ya no existe. GCC, al detectarlo, incluso cambia esa dirección por NULL, y leer v[0] es leer la dirección 0: Segmentation fault.

Hoy aprendes a pedir memoria que sobrevive a las funciones y cuyo tamaño decides mientras el programa se ejecuta: la memoria dinámica. Usaremos malloc, calloc, realloc y free con un ejemplo que nos acompañará en los tres artículos: las notas de una clase, cuando no sabes cuántos alumnos hay hasta el último momento. Si no tienes fresco lo de las direcciones y el *, repasa antes punteros en C y funciones en C. Cuando acabes, la práctica de memoria dinámica te espera con tres programas completos.

Dos sitios para guardar datos: la pila y el heap

Cuando ejecutas un programa en C, sus variables pueden vivir en dos zonas de memoria muy distintas.

La pila (stack) es donde viven las variables locales y los parámetros. Cada vez que llamas a una función se apila un «marco» con sus variables, y cuando la función termina, ese marco se quita entero. Es rápida y automática: no tienes que hacer nada para liberarla. A cambio, tiene dos limitaciones:

  • El tamaño de lo que guardas se fija al compilar: double notas[30] son 30 casillas y punto.
  • Lo que hay en el marco de una función muere con ella.

El heap (montón, o almacenamiento libre) es una zona grande que el programa puede pedir en tiempo de ejecución. Pides los bytes que necesites, cuando los necesites, y siguen ahí hasta que tú digas que ya no los quieres. Eso es la memoria dinámica.

La pila y el heapA la izquierda, la pila: el marco de crear_notas con su vector local, que desaparece al hacer return, y el marco de main con el puntero notas. A la derecha, el heap: un bloque de 6 doubles reservado con malloc, al que apunta notas.PILA (stack)HEAP (memoria dinámica)crear_notas()7.54.09.0se destruye al hacer returnmain()double *notasmalloc(6 * sizeof(double))7.54.09.06.53.58.001234548 bytes · vive hasta free(notas)Tamaño fijo · se libera solaTamaño al ejecutar · la liberas tú
Lo local muere con su función; lo que pides con malloc vive en el heap hasta que tú haces free.

Fíjate en el dibujo: a la izquierda, el marco de crear_notas con su vector local, que se borra al hacer return (eso es lo que rompió el gancho). Abajo, el marco de main con un puntero. A la derecha, el bloque del heap: el puntero de main apunta a él, y ese bloque no depende de ninguna función.

En C no hay recolector de basura como en Python o Java. Si pides memoria del heap, tú eres responsable de devolverla. Es el precio de tener el control.

¿Cuándo necesito memoria dinámica?

Situación¿Pila o heap?Por qué
Sé el tamaño al escribir el código (int dias[7])PilaEs fijo, la pila es más rápida y se libera sola
El tamaño lo dice el usuario o un archivo al ejecutarHeapLa pila necesita el tamaño al compilar
Una función tiene que devolver un vectorHeapLo local muere con la función
El vector tiene que crecer o encogerHeapSolo realloc cambia el tamaño de un bloque
Muchos datos (millones de casillas)HeapLa pila es pequeña (unos pocos MB)

sizeof: malloc cuenta bytes, no casillas

Antes de pedir memoria hay que saber cuánta pedir, y aquí está la trampa número uno del tema: las funciones de memoria dinámica trabajan en bytes. Si quieres 6 notas double, no pides 6: pides 6 × lo que ocupa un double. Para eso está sizeof.

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

int main(void) {
    double fijas[5] = {7.5, 4.0, 9.0, 6.5, 8.0};  /* estático: 5 casillas para siempre */
    double *din = malloc(5 * sizeof(double));      /* dinámico: 5 casillas pedidas al heap */
    if (din == NULL) return 1;                     /* sin memoria: no seguimos */

    printf("sizeof(double) = %zu bytes\n", sizeof(double));
    printf("sizeof(fijas)  = %zu bytes (5 x 8)\n", sizeof(fijas));
    printf("sizeof(din)    = %zu bytes (es un puntero)\n", sizeof(din));
    printf("bytes pedidos  = %zu\n", 5 * sizeof(double));
    printf("casillas fijas = %zu\n", sizeof(fijas) / sizeof(fijas[0]));

    free(din);   /* lo que se pide con malloc se devuelve con free */
    return 0;
}

Salida:

sizeof(double) = 8 bytes
sizeof(fijas)  = 40 bytes (5 x 8)
sizeof(din)    = 8 bytes (es un puntero)
bytes pedidos  = 40
casillas fijas = 5

Vamos línea a línea:

  • sizeof(double) vale 8: cada double ocupa 8 bytes en un ordenador de hoy.
  • sizeof(fijas) vale 40: es un vector de verdad, así que sizeof mide el vector entero, 5 × 8 = 40.
  • sizeof(din) vale 8, no 40. din no es un vector, es un puntero, y un puntero ocupa 8 bytes apunte a lo que apunte. sizeof no tiene ni idea de cuántos bytes hay al otro lado de la flecha.
  • 5 * sizeof(double) = 5 × 8 = 40: los bytes que hemos pedido de verdad.
  • sizeof(fijas) / sizeof(fijas[0]) = 40 / 8 = 5: el truco clásico para contar casillas… que solo funciona con vectores, nunca con punteros.
malloc cuenta bytes, no casillasUna regla de 48 bytes. Arriba, malloc(6) solo reserva los 6 primeros bytes. Abajo, malloc(6 * sizeof(double)) reserva los 48 bytes, agrupados en 6 casillas de 8 bytes, de notas[0] a notas[5].malloc(6)6 bytes: ni siquiera cabe un doublemalloc(6 * sizeof(double))48 bytes = 6 casillas de 8 bytesnotas[0]notas[1]notas[2]notas[3]notas[4]notas[5]1 casilla = sizeof(double) = 8 bytes
malloc recibe bytes: si quieres 6 doubles, multiplica por sizeof(double).

El dibujo lo deja claro: malloc(6) te da 6 bytes, que no llegan ni a una nota. malloc(6 * sizeof(double)) te da 48 bytes, justo 6 casillas de 8.

Escribir malloc(n) pensando en «n notas». El compilador no se queja, el programa a veces parece funcionar… y estás escribiendo en memoria que no es tuya. Lo veremos en directo en los ejercicios.

En lugar de repetir el tipo, escribe malloc(n * sizeof *notas). sizeof *notas es «lo que ocupa aquello a lo que apunta notas«. Si un día cambias double por float, la línea sigue siendo correcta sin tocarla.

malloc: pedir memoria en tiempo de ejecución

malloc (de memory allocation) está en <stdlib.h>. Recibe un número de bytes y devuelve un puntero al principio del bloque reservado, o NULL si no hay memoria suficiente:

double *notas = malloc(n * sizeof(double));

Lo que devuelve es un void *, un «puntero a lo que sea». En C se convierte solo al tipo de la variable, por eso no hace falta el cast. En tus apuntes verás (int*) malloc(...): es lo mismo, solo que más largo (en C++ sí es obligatorio).

Este programa reserva sitio para un número de notas que «llega» al ejecutar:

#include <stdio.h>
#include <stdlib.h>   /* aquí viven malloc, calloc, realloc y free */

int main(void) {
    int n = 6;   /* imagina que lo ha tecleado el profe: no lo sabíamos al compilar */
    const double datos[] = {7.5, 4.0, 9.0, 6.5, 3.5, 8.0};

    /* n casillas de double = n * sizeof(double) bytes (no n bytes) */
    double *notas = malloc(n * sizeof(double));
    if (notas == NULL) {            /* malloc devuelve NULL si no hay memoria */
        printf("No hay memoria para %d notas\n", n);
        return 1;
    }

    double suma = 0;
    for (int i = 0; i < n; i++) {
        notas[i] = datos[i];        /* se usa como un vector normal: notas[i] */
        suma += notas[i];
    }
    printf("Reservados %zu bytes para %d notas\n", n * sizeof(double), n);
    printf("Media: %.2f\n", suma / n);

    free(notas);     /* devolvemos la memoria */
    notas = NULL;    /* y dejamos claro que ya no apunta a nada válido */
    return 0;
}

Salida:

Reservados 48 bytes para 6 notas
Media: 6.42

Entre líneas:

  • **int n = 6;** hace de «lo que teclea el profe». Con scanf funcionaría igual: el tamaño se decide al ejecutar, que es lo importante.
  • **if (notas == NULL)** no es paranoia: si malloc no consigue memoria devuelve NULL, y usar notas[i] sobre NULL es un Segmentation fault asegurado. Siempre se comprueba.
  • **notas[i]**: una vez reservado, el bloque se usa exactamente igual que un vector. Recuerda de punteros en C que notas[i] es lo mismo que *(notas + i).
  • Los bytes: 6 × 8 = 48, como dice la salida.
  • La media: 7.5 + 4.0 + 9.0 + 6.5 + 3.5 + 8.0 = 38.5, y 38.5 / 6 = 6.4166…, que con %.2f se imprime como 6.42.
  • **free(notas); notas = NULL;** devuelve el bloque y deja el puntero «desactivado». Lo explicamos bien más abajo.

malloc no pone a cero lo que reserva. Las casillas recién pedidas contienen lo que hubiera antes en esa memoria (basura). Si las lees antes de escribirlas, el resultado es imprevisible.

calloc: memoria que empieza a cero

calloc (de contiguous allocation) hace lo mismo que malloc con dos diferencias:

  1. Recibe dos argumentos: cuántas casillas y cuánto ocupa cada una: calloc(11, sizeof(int)).
  2. Pone todos los bytes a cero.

¿Cuándo importa eso? Cuando el bloque va a ser una colección de contadores. Este programa hace un histograma de notas: un contador por cada nota de 0 a 10.

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

int main(void) {
    const int notas[] = {5, 7, 7, 4, 9, 5, 6, 7, 3, 5, 10, 7};
    int n = sizeof(notas) / sizeof(notas[0]);   /* 12 notas */

    /* 11 contadores (notas 0..10) que TIENEN que empezar a cero: trabajo para calloc */
    int *cuantos = calloc(11, sizeof(int));
    if (cuantos == NULL) return 1;

    for (int i = 0; i < n; i++)
        cuantos[notas[i]]++;        /* la nota hace de índice de su contador */

    for (int nota = 0; nota <= 10; nota++) {
        printf("%2d | ", nota);
        for (int k = 0; k < cuantos[nota]; k++)
            printf("*");             /* una estrella por alumno con esa nota */
        printf(" (%d)\n", cuantos[nota]);
    }

    free(cuantos);
    return 0;
}

Salida:

 0 |  (0)
 1 |  (0)
 2 |  (0)
 3 | * (1)
 4 | * (1)
 5 | *** (3)
 6 | * (1)
 7 | **** (4)
 8 |  (0)
 9 | * (1)
10 | * (1)

La línea clave es cuantos[notas[i]]++: la nota hace de índice. Cuando llega un 7, se suma uno a cuantos[7]. Si los contadores no empezaran en cero, cada ++ sumaría sobre basura. Comprobemos dos filas a mano: en la lista hay tres 5 (posiciones 0, 5 y 9) y cuatro 7 (posiciones 1, 2, 7 y 11). Total: 1 + 1 + 3 + 1 + 4 + 1 + 1 = 12 notas, las mismas que tiene el vector.

malloc(n * tam)calloc(n, tam)
ArgumentosBytes totalesCasillas y tamaño de cada una
Contenido inicialBasuraTodo a cero
VelocidadAlgo más rápidoAlgo más lento (tiene que poner ceros)
Úsalo para…Datos que vas a rellenar enseguidaContadores, acumuladores, tablas que empiezan vacías

Con malloc + un bucle que ponga ceros conseguirías lo mismo, pero calloc lo hace en una línea y sin olvidos.

realloc: cambiar el tamaño sin perder los datos

Aquí está la gran ventaja de la memoria dinámica: un bloque puede crecer o encoger. realloc(puntero, nuevos_bytes) intenta ampliar el bloque donde está; si no puede, reserva uno nuevo, copia los datos y libera el viejo. Te devuelve la dirección del bloque bueno (que puede ser distinta) o NULL si no hay memoria.

En nuestra clase han llegado dos alumnos tarde:

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

void mostrar(const double *v, int n) {
    printf("[");
    for (int i = 0; i < n; i++)
        printf(i == 0 ? "%.1f" : ", %.1f", v[i]);
    printf("] (%d notas)\n", n);
}

int main(void) {
    int n = 4;
    double *notas = malloc(n * sizeof(double));
    if (notas == NULL) return 1;
    const double primeras[] = {7.5, 4.0, 9.0, 6.5};
    for (int i = 0; i < n; i++) notas[i] = primeras[i];
    mostrar(notas, n);

    /* Llegan 2 alumnos tarde: necesitamos 6 casillas */
    int nuevo_n = n + 2;
    double *tmp = realloc(notas, nuevo_n * sizeof(double));  /* resultado en un puntero TEMPORAL */
    if (tmp == NULL) {              /* si falla, notas sigue intacto: no perdemos nada */
        printf("No se pudo ampliar; sigo con %d notas\n", n);
        free(notas);
        return 1;
    }
    notas = tmp;                    /* solo con éxito actualizamos notas */
    notas[4] = 5.5;                 /* las casillas nuevas llegan sin inicializar: */
    notas[5] = 8.0;                 /* hay que rellenarlas nosotros */
    n = nuevo_n;
    mostrar(notas, n);              /* las 4 primeras notas siguen ahí */

    free(notas);
    return 0;
}

Salida:

[7.5, 4.0, 9.0, 6.5] (4 notas)
[7.5, 4.0, 9.0, 6.5, 5.5, 8.0] (6 notas)

Las 4 primeras notas siguen intactas después de crecer: realloc las ha conservado. Hagamos zoom a la parte delicada:

/* Llegan 2 alumnos tarde: necesitamos 6 casillas */
int nuevo_n = n + 2;
double *tmp = realloc(notas, nuevo_n * sizeof(double));  /* resultado en un puntero TEMPORAL */
if (tmp == NULL) {              /* si falla, notas sigue intacto: no perdemos nada */
    printf("No se pudo ampliar; sigo con %d notas\n", n);
    free(notas);
    return 1;
}
notas = tmp;                    /* solo con éxito actualizamos notas */
notas[4] = 5.5;                 /* las casillas nuevas llegan sin inicializar: */
notas[5] = 8.0;                 /* hay que rellenarlas nosotros */
n = nuevo_n;
mostrar(notas, n);              /* las 4 primeras notas siguen ahí */

¿Por qué ese tmp? Porque realloc puede fallar. Si escribes directamente notas = realloc(notas, ...) y devuelve NULL, acabas de sobrescribir tu único puntero al bloque viejo con NULL: has perdido las notas y además ese bloque se queda reservado sin que nadie pueda liberarlo (una fuga de memoria). Con el temporal, si falla, notas sigue apuntando a los datos de siempre.

realloc con un puntero temporalTres casos. Si realloc funciona, notas y tmp apuntan al bloque nuevo de 6 casillas con las 4 notas copiadas. Si falla con temporal, tmp vale NULL y notas sigue apuntando al bloque de 4. Si falla sin temporal, notas vale NULL y el bloque de 4 queda sin nadie que lo apunte.Bien: realloc funcionatmp = realloc(notas, …)7.54.09.06.5??notastmpdatos a salvo, 6 casillasBien: realloc fallatmp = realloc(notas, …)7.54.09.06.5notastmp = NULLnotas sigue valiendoMal: sin temporalnotas = realloc(notas, …)7.54.09.06.5nadie lo apuntanotas = NULLdatos perdidos + fuga
Guarda el resultado de realloc en tmp: si vale NULL, tu vector original sigue intacto.

Mira los tres casos del dibujo. En los dos de la izquierda, pase lo que pase, nunca pierdes el vector. En el de la derecha, un realloc fallido te deja con NULL y un bloque huérfano.

v = realloc(v, ...). Funciona el 99,9 % de las veces, y justo por eso cuesta tanto encontrarlo cuando falla. Usa siempre un puntero temporal.

Las casillas nuevas que añade realloc no se inicializan (como con malloc). Por eso el programa rellena notas[4] y notas[5] antes de mostrarlas.

Dos casos especiales que conviene conocer: realloc(NULL, bytes) se comporta como malloc(bytes), y encoger un bloque (pasar de 8 a 7 casillas, por ejemplo) también se hace con realloc. Lo usaremos en la práctica.

free: devolver lo que pediste

Todo bloque que pides con malloc, calloc o realloc se devuelve una vez con free(puntero). Ahora podemos arreglar el gancho: la función reserva en el heap, y main, que es quien usa el vector, lo libera.

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

/* Ahora el vector vive en el heap: sobrevive al return. Quien llama debe liberarlo. */
double *crear_notas(int n, const double *datos) {
    double *v = malloc(n * sizeof *v);   /* sizeof *v = tamaño de lo que apunta v */
    if (v == NULL) return NULL;          /* el fallo se le pasa a quien llama */
    for (int i = 0; i < n; i++)
        v[i] = datos[i];
    return v;                            /* devolvemos una dirección que sigue siendo válida */
}

int main(void) {
    const double datos[] = {7.5, 4.0, 9.0};
    double *v = crear_notas(3, datos);
    if (v == NULL) return 1;

    printf("Vector creado. Primera nota:\n");
    printf("%.1f\n", v[0]);

    free(v);       /* main lo pidió (a través de crear_notas): main lo libera */
    v = NULL;
    free(v);       /* free(NULL) no hace nada: por eso conviene poner NULL tras liberar */
    printf("Liberado; el segundo free(NULL) no hizo nada\n");
    return 0;
}

Salida:

Vector creado. Primera nota:
7.5
Liberado; el segundo free(NULL) no hizo nada

Esta vez la primera nota sale bien, porque el bloque está en el heap y no muere con crear_notas. Tres ideas importantes:

  1. Quien recibe el bloque es su dueño. crear_notas lo reserva, pero se lo entrega a main, y main tiene que liberarlo. Es buena costumbre decirlo en un comentario encima de la función.
  2. **free no cambia el puntero.** Después de free(v), v sigue guardando la misma dirección, pero esa memoria ya no es tuya: es un puntero colgante (dangling pointer). Usarlo es un error.
  3. **Por eso se pone v = NULL.** free(NULL) no hace nada, así que si alguien vuelve a liberar ese puntero por despiste, no pasa nada. Y leer v[0] con v a NULL falla enseguida y de forma clara, en lugar de devolver basura en silencio.

Las fugas de memoria y cómo verlas

¿Y si te olvidas del free? El programa no falla: simplemente esa memoria se queda reservada hasta que el programa termina. Si ocurre dentro de un bucle o en un programa que está horas encendido, la memoria se va acumulando. Es una fuga (memory leak). Este programa calcula la media sobre una copia y nunca la libera:

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

/* Calcula la media de una copia de las notas... y se olvida de liberar la copia */
double media_copia(const double *notas, int n) {
    double *copia = malloc(n * sizeof *copia);
    if (copia == NULL) return -1;
    double suma = 0;
    for (int i = 0; i < n; i++) {
        copia[i] = notas[i];
        suma += copia[i];
    }
    return suma / n;          /* FALTA free(copia): estos bytes se quedan reservados */
}

int main(void) {
    const double notas[] = {7.5, 4.0, 9.0, 6.5, 3.5, 8.0};
    for (int dia = 1; dia <= 3; dia++)   /* tres consultas = tres copias perdidas */
        printf("Consulta %d: media %.2f\n", dia, media_copia(notas, 6));
    return 0;
}

Normalmente no verías nada raro. Pero si compilas con el detector de errores de memoria de GCC, AddressSanitizer (gcc -Wall -std=c11 -g -fsanitize=address fuga.c -o fuga), al terminar te enseña la fuga. Esta es la salida (recortada a las líneas importantes):

Consulta 1: media 6.42
Consulta 2: media 6.42
Consulta 3: media 6.42
=================================================================
==831==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 144 byte(s) in 3 object(s) allocated from:
    #1 in media_copia fuga.c:6
    #2 in main fuga.c:19
SUMMARY: AddressSanitizer: 144 byte(s) leaked in 3 allocation(s).

Léelo de abajo arriba: se han perdido 144 bytes en 3 bloques. 3 consultas × 6 notas × 8 bytes = 144. Y te dice dónde se reservaron: en media_copia, línea 6 de fuga.c, llamada desde main en la línea 19. La solución es un free(copia); antes del return.

Acostúmbrate a compilar tus prácticas de memoria dinámica con -g -fsanitize=address mientras las pruebas. Detecta fugas, escrituras fuera del bloque y dobles free en el momento. En Fedora también tienes valgrind --leak-check=full ./programa, que hace algo parecido sin recompilar.

Errores típicos con memoria dinámica

Devolver la dirección de un vector local (el gancho). Si una función tiene que devolver un vector, resérvalo con malloc.

Olvidar sizeof: malloc(n) reserva n bytes, no n casillas.

No comprobar NULL después de malloc, calloc o realloc.

v = realloc(v, ...) sin puntero temporal: si falla, pierdes el vector y lo dejas como fuga.

Usar un puntero después de free o liberarlo dos veces. Después de free(v), escribe v = NULL.

Usar sizeof(puntero) para saber cuántas casillas tiene un vector dinámico. Siempre vale 8. El tamaño lo tienes que guardar tú en una variable (n).

Míralo paso a paso en Python Tutor

Python Tutor no es solo para Python: elige C (gcc) en el desplegable de lenguaje, pega este programa y pulsa Visualize Execution. Ejecuta el código línea a línea y dibuja la pila (Stack) a la izquierda y el heap (Heap) a la derecha, con flechas para los punteros. Es justo lo que necesitamos para ver la memoria dinámica.

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

int main(void) {
    int n = 3;
    double *notas = malloc(n * sizeof(double));
    if (notas == NULL) return 1;
    notas[0] = 7.5;
    notas[1] = 4.0;
    notas[2] = 9.0;

    double *tmp = realloc(notas, 5 * sizeof(double));
    if (tmp == NULL) { free(notas); return 1; }
    notas = tmp;
    notas[3] = 6.5;
    notas[4] = 8.0;
    n = 5;

    printf("Ultima nota: %.1f\n", notas[n - 1]);
    free(notas);
    notas = NULL;
    return 0;
}

Qué observar paso a paso:

  1. Tras el malloc, en la zona Heap aparece un bloque de 3 casillas sin valores conocidos, y notas (en la pila, dentro de main) es una flecha hacia él.
  2. Al ejecutar las tres asignaciones, el bloque se rellena con 7.5, 4.0 y 9.0. Fíjate en que notas no cambia: lo que cambia es lo que hay al otro lado de la flecha.
  3. Tras el realloc, el heap muestra un bloque de 5 casillas con las 3 notas copiadas. Según la herramienta, puede que el bloque haya cambiado de sitio: tmp apunta al bloque nuevo y, tras notas = tmp, también notas.
  4. Después de free(notas), el bloque desaparece del heap y notas pasa a ser una flecha a nada, hasta que notas = NULL lo deja explícitamente vacío.

Resumen

FunciónQué haceDevuelveOjo con…
malloc(bytes)Reserva un bloquePuntero al bloque o NULLNo inicializa; multiplica por sizeof
calloc(n, tam)Reserva n casillas de tam bytes, a ceroPuntero o NULLIdeal para contadores
realloc(p, bytes)Cambia el tamaño conservando los datosPuntero (quizá nuevo) o NULLUsa un temporal; lo nuevo no se inicializa
free(p)Devuelve el bloqueNadaUna vez por bloque; después, p = NULL

Tres ideas para quedarte

  1. Pila para lo fijo y local, heap para lo que decides al ejecutar o tiene que sobrevivir a una función.
  2. Bytes, no casillas: n * sizeof *v, y comprueba siempre NULL.
  3. **Cada bloque tiene un dueño y un único free.** realloc siempre con temporal y, después de liberar, NULL.

Ahora toca practicarlo: en la práctica de memoria dinámica en C montamos un acta de notas que se ajusta a las notas válidas, un vector que va creciendo según llegan alumnos y una tabla dinámica de notas por asignatura. Y en el siguiente tema del temario de IC2, los structs y las cadenas, verás que la memoria dinámica es lo que permite guardar tantos registros como haga falta.

Siguiente paso: prácticaMemoria dinámica en C — práctica con un acta de notas que se ajusta, un vector que crece al llegar alumnos y una tabla de notas en el heap (y el realloc que casi me deja sin datos)

¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

Sigue aprendiendo

Un comentario

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *