C

C

Tutorial C: Memoria estática vs dinámica

Aprende en C las diferencias entre memoria estática y dinámica, gestión con malloc, calloc, realloc y el uso de stack y heap para optimizar tus programas.

Aprende C y certifícate

Memoria estática: tamaño fijo en compilación

La memoria estática es aquella cuyo tamaño se determina durante la fase de compilación del programa y permanece fijo durante toda la ejecución. Cuando escribimos código en C, podemos declarar variables que ocuparán un espacio predefinido en la memoria del ordenador.

Características de la memoria estática

La memoria estática en C tiene varias características importantes:

  • Tamaño conocido: El compilador sabe exactamente cuánta memoria necesita reservar.
  • Asignación automática: No necesitamos solicitar esta memoria explícitamente.
  • Duración predecible: Dependiendo de dónde se declare, puede durar toda la ejecución del programa o solo mientras se ejecuta una función.

Variables estáticas en C

En C, las variables con asignación estática de memoria incluyen:

  • Variables globales: Declaradas fuera de cualquier función
  • Variables locales estáticas: Declaradas dentro de funciones con la palabra clave static
  • Arrays de tamaño fijo: Declarados con un tamaño constante

Veamos un ejemplo sencillo de memoria estática con un array:

#include <stdio.h>

int main() {
    // Array con asignación estática de memoria
    int numeros[5] = {10, 20, 30, 40, 50};
    
    printf("El array ocupa %lu bytes en memoria\n", sizeof(numeros));
    
    // Accediendo a los elementos del array
    for(int i = 0; i < 5; i++) {
        printf("numeros[%d] = %d\n", i, numeros[i]);
    }
    
    return 0;
}

En este ejemplo, numeros es un array de 5 enteros que ocupa un espacio fijo en memoria (20 bytes en sistemas donde cada int ocupa 4 bytes). Este tamaño se determina en tiempo de compilación y no puede cambiar durante la ejecución.

Limitaciones de la memoria estática

La memoria estática, aunque sencilla de usar, presenta algunas limitaciones importantes:

  • Tamaño fijo: No podemos cambiar el tamaño de un array estático una vez declarado.
  • Conocimiento previo: Necesitamos saber de antemano cuánta memoria vamos a necesitar.
  • Posible desperdicio: Si reservamos más memoria de la necesaria, el resto queda inutilizada.

Por ejemplo, si declaramos un array para almacenar hasta 100 nombres pero solo usamos 10, estamos desperdiciando memoria:

#include <stdio.h>

int main() {
    // Reservamos espacio para 100 nombres, aunque solo usaremos unos pocos
    char nombres[100][50];  // 100 nombres de hasta 50 caracteres cada uno
    
    // Solo usamos los primeros 3 espacios
    strcpy(nombres[0], "Ana");
    strcpy(nombres[1], "Carlos");
    strcpy(nombres[2], "Elena");
    
    // Mostramos los nombres utilizados
    for(int i = 0; i < 3; i++) {
        printf("Nombre %d: %s\n", i+1, nombres[i]);
    }
    
    return 0;
}

En este caso, hemos reservado estáticamente memoria para 100 nombres (5000 bytes en total), pero solo estamos utilizando 3 espacios, lo que resulta en un desperdicio de recursos.

Variables globales y estáticas

Las variables globales y las variables locales declaradas como static también utilizan memoria estática:

#include <stdio.h>

// Variable global (memoria estática)
int contador_global = 0;

void incrementar() {
    // Variable estática local (memoria estática)
    static int contador_local = 0;
    
    contador_global++;
    contador_local++;
    
    printf("Global: %d, Local: %d\n", contador_global, contador_local);
}

int main() {
    for(int i = 0; i < 3; i++) {
        incrementar();
    }
    
    return 0;
}

En este ejemplo, tanto contador_global como contador_local ocupan memoria estática, pero tienen diferentes ámbitos de visibilidad. La variable local estática mantiene su valor entre llamadas a la función, a diferencia de las variables locales normales.

Matrices multidimensionales estáticas

Las matrices multidimensionales también pueden declararse de forma estática:

#include <stdio.h>

int main() {
    // Matriz 3x3 con asignación estática
    int matriz[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    
    // Recorremos la matriz
    for(int i = 0; i < 3; i++) {
        for(int j = 0; j < 3; j++) {
            printf("%d ", matriz[i][j]);
        }
        printf("\n");
    }
    
    return 0;
}

Esta matriz ocupa un espacio fijo en memoria (9 enteros), determinado en tiempo de compilación.

Constantes y memoria estática

Las constantes en C también utilizan memoria estática:

#include <stdio.h>

#define MAX_ESTUDIANTES 30

int main() {
    const double PI = 3.14159;
    
    // Array de tamaño fijo basado en una constante
    int calificaciones[MAX_ESTUDIANTES];
    
    printf("Valor de PI: %f\n", PI);
    printf("Tamaño del array: %lu bytes\n", sizeof(calificaciones));
    
    return 0;
}

Tanto PI como MAX_ESTUDIANTES son valores que no cambian durante la ejecución, y el array calificaciones tiene un tamaño fijo determinado por la constante.

Cuándo usar memoria estática

La memoria estática es ideal para:

  • Datos de tamaño conocido que no cambiarán durante la ejecución
  • Constantes y valores predefinidos
  • Pequeñas estructuras de datos con tamaño limitado
  • Variables globales que necesitan mantener su valor durante toda la ejecución

Es importante elegir adecuadamente entre memoria estática y dinámica según las necesidades de nuestro programa, considerando factores como el tamaño de los datos, la duración requerida y la flexibilidad necesaria.

Memoria dinámica: tamaño en ejecución

A diferencia de la memoria estática, la memoria dinámica permite asignar espacio durante la ejecución del programa según las necesidades que vayan surgiendo. Esta característica resulta fundamental cuando no conocemos de antemano el tamaño exacto de los datos que vamos a manejar o cuando necesitamos estructuras de datos que crezcan o disminuyan.

Funciones para gestión de memoria dinámica

En C, disponemos de varias funciones de la biblioteca estándar para trabajar con memoria dinámica:

  • malloc(): Asigna un bloque de memoria del tamaño especificado
  • calloc(): Asigna e inicializa a cero un bloque de memoria
  • realloc(): Cambia el tamaño de un bloque de memoria previamente asignado
  • free(): Libera un bloque de memoria previamente asignado

Todas estas funciones requieren incluir la cabecera <stdlib.h>.

Asignación básica con malloc()

La función malloc() (memory allocation) es la más utilizada para solicitar memoria dinámica:

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

int main() {
    // Solicitamos memoria para 5 enteros
    int *numeros = (int*) malloc(5 * sizeof(int));
    
    // Verificamos si la asignación fue exitosa
    if (numeros == NULL) {
        printf("Error: No se pudo asignar memoria\n");
        return 1;
    }
    
    // Usamos la memoria asignada
    for (int i = 0; i < 5; i++) {
        numeros[i] = i * 10;
    }
    
    // Mostramos los valores
    for (int i = 0; i < 5; i++) {
        printf("numeros[%d] = %d\n", i, numeros[i]);
    }
    
    // Liberamos la memoria cuando ya no la necesitamos
    free(numeros);
    
    return 0;
}

En este ejemplo:

  1. Solicitamos memoria para almacenar 5 enteros
  2. Comprobamos si la asignación fue exitosa (siempre es recomendable)
  3. Utilizamos la memoria como si fuera un array normal
  4. Finalmente liberamos la memoria con free()

Inicialización con calloc()

La función calloc() (clear allocation) asigna memoria e inicializa todos los bits a cero:

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

int main() {
    // Solicitamos memoria para 5 enteros e inicializamos a cero
    int *numeros = (int*) calloc(5, sizeof(int));
    
    if (numeros == NULL) {
        printf("Error: No se pudo asignar memoria\n");
        return 1;
    }
    
    // Mostramos los valores (todos serán 0)
    printf("Valores inicializados por calloc:\n");
    for (int i = 0; i < 5; i++) {
        printf("numeros[%d] = %d\n", i, numeros[i]);
    }
    
    free(numeros);
    return 0;
}

La diferencia principal entre malloc() y calloc() es que esta última inicializa la memoria a cero, mientras que malloc() simplemente la asigna sin inicializar.

Redimensionamiento con realloc()

Una de las ventajas clave de la memoria dinámica es la posibilidad de cambiar su tamaño durante la ejecución:

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

int main() {
    int n = 5;
    
    // Asignamos memoria inicialmente para 5 enteros
    int *datos = (int*) malloc(n * sizeof(int));
    
    if (datos == NULL) {
        printf("Error en la asignación inicial\n");
        return 1;
    }
    
    // Llenamos el array
    for (int i = 0; i < n; i++) {
        datos[i] = i + 1;
    }
    
    // Decidimos ampliar el array a 8 elementos
    n = 8;
    datos = (int*) realloc(datos, n * sizeof(int));
    
    if (datos == NULL) {
        printf("Error en la reasignación\n");
        return 1;
    }
    
    // Añadimos valores a las nuevas posiciones
    for (int i = 5; i < n; i++) {
        datos[i] = i + 1;
    }
    
    // Mostramos todos los valores
    for (int i = 0; i < n; i++) {
        printf("datos[%d] = %d\n", i, datos[i]);
    }
    
    free(datos);
    return 0;
}

La función realloc() intenta ampliar o reducir el bloque de memoria manteniendo los datos originales. Si es necesario, puede mover los datos a una nueva ubicación con más espacio.

Arrays dinámicos de tamaño variable

Una aplicación común de la memoria dinámica es crear arrays cuyo tamaño se determina durante la ejecución:

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

int main() {
    int tamano;
    int *array;
    
    // El usuario decide el tamaño
    printf("Introduce el tamaño del array: ");
    scanf("%d", &tamano);
    
    // Asignamos memoria según el tamaño indicado
    array = (int*) malloc(tamano * sizeof(int));
    
    if (array == NULL) {
        printf("Error: No hay suficiente memoria\n");
        return 1;
    }
    
    // Llenamos el array con valores
    for (int i = 0; i < tamano; i++) {
        array[i] = i * i;
    }
    
    // Mostramos el contenido
    for (int i = 0; i < tamano; i++) {
        printf("array[%d] = %d\n", i, array[i]);
    }
    
    free(array);
    return 0;
}

Este enfoque es mucho más flexible que usar arrays estáticos, ya que podemos adaptar el tamaño a las necesidades reales del programa.

Matrices dinámicas

También podemos crear matrices (arrays bidimensionales) de forma dinámica:

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

int main() {
    int filas = 3;
    int columnas = 4;
    
    // Asignamos memoria para las filas (array de punteros)
    int **matriz = (int**) malloc(filas * sizeof(int*));
    
    if (matriz == NULL) {
        printf("Error al asignar memoria para filas\n");
        return 1;
    }
    
    // Asignamos memoria para cada columna
    for (int i = 0; i < filas; i++) {
        matriz[i] = (int*) malloc(columnas * sizeof(int));
        
        if (matriz[i] == NULL) {
            printf("Error al asignar memoria para columnas\n");
            
            // Liberamos la memoria ya asignada
            for (int j = 0; j < i; j++) {
                free(matriz[j]);
            }
            free(matriz);
            
            return 1;
        }
    }
    
    // Llenamos la matriz
    for (int i = 0; i < filas; i++) {
        for (int j = 0; j < columnas; j++) {
            matriz[i][j] = i * columnas + j;
        }
    }
    
    // Mostramos la matriz
    for (int i = 0; i < filas; i++) {
        for (int j = 0; j < columnas; j++) {
            printf("%2d ", matriz[i][j]);
        }
        printf("\n");
    }
    
    // Liberamos memoria (importante: primero las filas, luego la matriz)
    for (int i = 0; i < filas; i++) {
        free(matriz[i]);
    }
    free(matriz);
    
    return 0;
}

Crear matrices dinámicas requiere más cuidado, especialmente al liberar la memoria para evitar fugas de memoria.

Gestión de cadenas dinámicas

La memoria dinámica es ideal para manejar cadenas de texto de longitud variable:

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

int main() {
    char *nombre;
    int longitud;
    
    printf("¿Cuántos caracteres tiene tu nombre? ");
    scanf("%d", &longitud);
    
    // Consumimos el salto de línea pendiente
    getchar();
    
    // Asignamos memoria para la cadena (+1 para el carácter nulo)
    nombre = (char*) malloc((longitud + 1) * sizeof(char));
    
    if (nombre == NULL) {
        printf("Error: No se pudo asignar memoria\n");
        return 1;
    }
    
    printf("Introduce tu nombre: ");
    fgets(nombre, longitud + 1, stdin);
    
    // Eliminamos el posible salto de línea capturado por fgets
    if (nombre[strlen(nombre) - 1] == '\n') {
        nombre[strlen(nombre) - 1] = '\0';
    }
    
    printf("Hola, %s!\n", nombre);
    
    free(nombre);
    return 0;
}

Errores comunes al usar memoria dinámica

Al trabajar con memoria dinámica, es importante evitar estos errores frecuentes:

  • Fugas de memoria: Olvidar liberar memoria con free()
  • Acceso después de liberar: Usar memoria después de llamar a free()
  • Desbordamiento de buffer: Acceder más allá de los límites asignados
  • Doble liberación: Llamar a free() dos veces sobre el mismo puntero
#include <stdio.h>
#include <stdlib.h>

// Ejemplo de código con errores comunes (NO HACER ESTO)
void ejemploConErrores() {
    int *array = (int*) malloc(5 * sizeof(int));
    
    // Error 1: No verificamos si malloc falló
    
    array[0] = 10;
    array[1] = 20;
    
    // Error 2: Acceso fuera de límites
    array[10] = 100;  // ¡Peligroso! Fuera del espacio asignado
    
    // Error 3: Olvidar liberar memoria (fuga de memoria)
    // free(array) falta aquí
}

// Versión corregida
void ejemploCorrecto() {
    int *array = (int*) malloc(5 * sizeof(int));
    
    // Verificamos si malloc tuvo éxito
    if (array == NULL) {
        printf("Error: No se pudo asignar memoria\n");
        return;
    }
    
    // Accedemos solo dentro de los límites
    for (int i = 0; i < 5; i++) {
        array[i] = i * 10;
    }
    
    // Liberamos la memoria cuando terminamos
    free(array);
    
    // Evitamos usar el puntero después de liberarlo
    array = NULL;
}

int main() {
    ejemploCorrecto();
    return 0;
}

Ventajas de la memoria dinámica

La memoria dinámica ofrece varias ventajas importantes:

  • Flexibilidad: Podemos ajustar el uso de memoria según las necesidades reales
  • Eficiencia: Solo utilizamos la memoria que realmente necesitamos
  • Adaptabilidad: Podemos responder a requisitos cambiantes durante la ejecución
  • Estructuras de datos avanzadas: Permite implementar listas enlazadas, árboles, grafos, etc.

Comparación con memoria estática

Veamos un ejemplo que ilustra la diferencia entre usar memoria estática y dinámica:

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

void enfoqueEstatico() {
    // Enfoque estático: tamaño fijo, posible desperdicio
    int numeros[100];  // Reservamos 100 espacios aunque quizás necesitemos menos
    int n = 0;
    
    printf("¿Cuántos números quieres introducir? (máx 100): ");
    scanf("%d", &n);
    
    if (n > 100) {
        printf("Error: no puedes introducir más de 100 números\n");
        return;
    }
    
    for (int i = 0; i < n; i++) {
        numeros[i] = i;
    }
}

void enfoqueDinamico() {
    // Enfoque dinámico: tamaño exacto según necesidad
    int n = 0;
    int *numeros;
    
    printf("¿Cuántos números quieres introducir? (sin límite): ");
    scanf("%d", &n);
    
    numeros = (int*) malloc(n * sizeof(int));
    
    if (numeros == NULL) {
        printf("Error: No se pudo asignar memoria\n");
        return;
    }
    
    for (int i = 0; i < n; i++) {
        numeros[i] = i;
    }
    
    free(numeros);
}

int main() {
    // Prueba ambos enfoques
    enfoqueEstatico();
    enfoqueDinamico();
    
    return 0;
}

El enfoque dinámico nos permite adaptarnos a cualquier cantidad de datos sin desperdiciar memoria ni imponer límites artificiales.

Stack vs Heap explicado simple

Cuando programamos en C, nuestro programa utiliza dos áreas principales de memoria para almacenar datos: el stack (pila) y el heap (montículo). Estas áreas tienen características muy diferentes y entender cómo funcionan nos ayudará a escribir programas más eficientes.

¿Qué es el Stack?

El stack es una región de memoria que funciona como una pila de platos: el último elemento que colocas es el primero que puedes retirar. Esta región tiene las siguientes características:

  • Organización automática: El sistema gestiona automáticamente la asignación y liberación de memoria
  • Tamaño limitado: Tiene un tamaño máximo predefinido (generalmente algunos MB)
  • Acceso rápido: Las operaciones en el stack son muy rápidas
  • Estructura ordenada: Los datos se almacenan de forma contigua y ordenada

En el stack se guardan:

  • Variables locales (las que declaramos dentro de funciones)
  • Parámetros de funciones
  • Dirección de retorno de las funciones
  • Variables estáticas (aunque con un comportamiento especial)
#include <stdio.h>

void funcionEjemplo() {
    // Estas variables se almacenan en el stack
    int numero = 42;
    char letra = 'A';
    float precio = 9.99;
    
    printf("Variables en el stack: %d, %c, %.2f\n", numero, letra, precio);
    
    // Al terminar la función, estas variables desaparecen automáticamente
}

int main() {
    funcionEjemplo();
    return 0;
}

¿Qué es el Heap?

El heap es una región de memoria más flexible que funciona como un gran almacén donde podemos solicitar y devolver espacio según lo necesitemos. Sus características son:

  • Gestión manual: Nosotros debemos solicitar y liberar la memoria explícitamente
  • Tamaño flexible: Puede crecer según las necesidades (limitado por la memoria disponible)
  • Acceso más lento: Las operaciones son algo más lentas que en el stack
  • Estructura menos ordenada: Los datos pueden estar dispersos

En el heap se guardan:

  • Datos creados con malloc(), calloc() o realloc()
  • Estructuras de datos de tamaño variable
  • Objetos grandes o que necesitan existir más allá del ámbito de una función
#include <stdio.h>
#include <stdlib.h>

int* crearArray(int tamano) {
    // Esta memoria se asigna en el heap
    int* array = (int*)malloc(tamano * sizeof(int));
    
    // Inicializamos el array
    if (array != NULL) {
        for (int i = 0; i < tamano; i++) {
            array[i] = i * 10;
        }
    }
    
    // Devolvemos el puntero a la memoria del heap
    return array;
}

int main() {
    int* numeros = crearArray(5);
    
    if (numeros != NULL) {
        printf("Array creado en el heap: ");
        for (int i = 0; i < 5; i++) {
            printf("%d ", numeros[i]);
        }
        printf("\n");
        
        // Importante: debemos liberar la memoria manualmente
        free(numeros);
    }
    
    return 0;
}

Analogía: La oficina y el almacén

Para entender mejor la diferencia entre stack y heap, podemos usar una analogía:

  • El stack es como tu escritorio de trabajo: un espacio limitado donde colocas los documentos con los que estás trabajando ahora mismo. Es fácil y rápido acceder a ellos, pero cuando terminas una tarea, retiras esos documentos para hacer espacio para la siguiente tarea.

  • El heap es como un almacén grande: puedes guardar muchas cosas y mantenerlas allí por mucho tiempo. Tienes que pedir espacio específicamente, recordar dónde pusiste las cosas y, cuando ya no las necesitas, debes indicar que ese espacio puede ser utilizado para otras cosas.

Diferencias clave entre Stack y Heap

Característica Stack Heap
Gestión Automática Manual (malloc/free)
Tamaño Limitado Flexible (más grande)
Velocidad Muy rápido Más lento
Fragmentación No ocurre Puede fragmentarse
Orden de datos Contiguo Puede estar disperso
Duración Temporal (ámbito) Hasta que se libere
Uso típico Variables locales Datos dinámicos

¿Cuándo usar cada uno?

  • Usa el stack cuando:

  • Trabajas con datos pequeños de tamaño conocido

  • Necesitas variables que solo existan dentro de una función

  • Buscas el mejor rendimiento para operaciones simples

  • Usa el heap cuando:

  • No conoces el tamaño de los datos de antemano

  • Necesitas que los datos persistan después de salir de una función

  • Trabajas con estructuras de datos grandes o que cambian de tamaño

Visualizando Stack y Heap en acción

Veamos un ejemplo que utiliza ambas regiones de memoria:

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

void procesarDatos() {
    // Variables en el stack
    int contador = 0;
    char bufferPequeno[50];
    
    // Datos en el heap
    char* textoGrande = (char*)malloc(1000 * sizeof(char));
    
    if (textoGrande == NULL) {
        printf("Error: No se pudo asignar memoria\n");
        return;
    }
    
    // Usamos ambas áreas de memoria
    strcpy(bufferPequeno, "Datos en el stack");
    strcpy(textoGrande, "Datos más grandes almacenados en el heap");
    
    printf("Stack: %s\n", bufferPequeno);
    printf("Heap: %s\n", textoGrande);
    
    // Liberamos la memoria del heap
    free(textoGrande);
    
    // No necesitamos liberar bufferPequeno, se libera automáticamente
}

int main() {
    procesarDatos();
    return 0;
}

En este ejemplo:

  1. contador y bufferPequeno se almacenan en el stack y se liberan automáticamente al salir de la función
  2. textoGrande apunta a memoria en el heap que debemos liberar manualmente con free()

Errores comunes relacionados con Stack y Heap

Desbordamiento del Stack (Stack Overflow)

Ocurre cuando intentamos usar más memoria de la disponible en el stack:

#include <stdio.h>

// Esta función puede causar stack overflow
void funcionRecursivaPeligrosa(int n) {
    // Array grande en el stack
    char granArray[1000000];  // ¡Peligro! Ocupa mucho espacio en el stack
    
    // Llamada recursiva sin condición de salida adecuada
    if (n > 0) {
        funcionRecursivaPeligrosa(n - 1);
    }
}

// Versión segura usando el heap
void funcionRecursivaSegura(int n) {
    if (n > 0) {
        // Usamos el heap para datos grandes
        char* granArray = (char*)malloc(1000000);
        
        if (granArray != NULL) {
            // Hacemos algo con el array...
            
            // Liberamos antes de la llamada recursiva
            free(granArray);
            
            funcionRecursivaSegura(n - 1);
        }
    }
}

int main() {
    // Esto probablemente causará un error
    // funcionRecursivaPeligrosa(10);
    
    // Esta versión es más segura
    funcionRecursivaSegura(10);
    
    return 0;
}

Fugas de memoria (Memory Leaks)

Ocurren cuando asignamos memoria en el heap pero olvidamos liberarla:

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

// Función con fuga de memoria
void funcionConFuga() {
    int* datos = (int*)malloc(100 * sizeof(int));
    
    if (datos != NULL) {
        // Hacemos algo con los datos...
        
        // ¡Error! Olvidamos liberar la memoria
        // free(datos);
    }
}

// Función corregida
void funcionCorrecta() {
    int* datos = (int*)malloc(100 * sizeof(int));
    
    if (datos != NULL) {
        // Hacemos algo con los datos...
        
        // Liberamos correctamente
        free(datos);
    }
}

int main() {
    // Si llamamos a esta función muchas veces, perderemos memoria
    // funcionConFuga();
    
    // Esta versión es correcta
    funcionCorrecta();
    
    return 0;
}

Consejos prácticos

  • Usa el stack para variables pequeñas y temporales
  • Usa el heap para datos grandes o de larga duración
  • Siempre verifica el resultado de malloc() y funciones similares
  • Libera la memoria del heap cuando ya no la necesites
  • Establece a NULL los punteros después de liberarlos para evitar accesos incorrectos
  • Evita arrays muy grandes en el stack para prevenir desbordamientos
  • Mantén un equilibrio: el stack es más rápido, pero el heap es más flexible
#include <stdio.h>
#include <stdlib.h>

int main() {
    // Buena práctica: verificar malloc y establecer NULL después de free
    int* datos = (int*)malloc(10 * sizeof(int));
    
    if (datos == NULL) {
        printf("Error: No se pudo asignar memoria\n");
        return 1;
    }
    
    // Usamos la memoria...
    for (int i = 0; i < 10; i++) {
        datos[i] = i;
    }
    
    // Liberamos y establecemos a NULL
    free(datos);
    datos = NULL;  // Buena práctica
    
    // Ahora es seguro verificar
    if (datos == NULL) {
        printf("La memoria ha sido liberada correctamente\n");
    }
    
    return 0;
}

Entender la diferencia entre stack y heap te ayudará a tomar mejores decisiones sobre cómo organizar la memoria en tus programas, lo que resultará en código más eficiente y con menos errores.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende C online

Todas las lecciones de C

Accede a todas las lecciones de C y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a C y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender las características y limitaciones de la memoria estática en C.
  • Aprender a utilizar funciones de asignación dinámica de memoria como malloc, calloc, realloc y free.
  • Diferenciar entre las áreas de memoria stack y heap, y sus usos apropiados.
  • Identificar errores comunes en la gestión de memoria dinámica y cómo evitarlos.
  • Aplicar buenas prácticas para gestionar memoria y evitar fugas o desbordamientos.