C

C

Tutorial C: Gestión de memoria dinámica

C: Gestión eficiente de memoria dinámica usando malloc, calloc, realloc y free. Evita fugas y gestiona errores con punteros nulos.

Aprende C y certifícate

Reservar memoria en tiempo de ejecución

Formas de reservar memoria en C:

  • La función malloc() permite asignar un bloque continuo de memoria durante la ejecución. Toma como parámetro el tamaño requerido en bytes y devuelve un puntero de tipo void*. Un uso habitual consiste en asignar este puntero a una variable del tipo deseado mediante conversión explícita. Es importante verificar que la asignación no devuelva NULL antes de usar el bloque asignado.
  • La función calloc() se utiliza para reservar memoria para un número específico de elementos, inicializando cada byte a cero. Recibe dos argumentos: la cantidad de elementos y el tamaño de cada elemento en bytes. Al igual que con malloc(), conviene controlar que el resultado no sea NULL, ya que esto implicaría que la asignación no ha sido satisfactoria.
  • La función realloc() facilita redimensionar un bloque de memoria previamente asignado. Si se solicita un tamaño mayor, esta función procurará mantener el contenido existente y reservar espacio adicional. Si se llama con un tamaño menor, ajusta el bloque conforme a la nueva longitud. En caso de que no sea posible redimensionar en la misma dirección de memoria, se copiará el contenido al nuevo bloque y el anterior se liberará de manera automática, siempre que no sea NULL.
  • La función free() se encarga de devolver al sistema el bloque asignado con malloc(), calloc() o realloc(). Para un manejo adecuado, se recomienda guardar una referencia al área de memoria y liberar cada bloque cuando ya no sea necesario. Intentar liberar un puntero que no apunte a un bloque válido o que ya se haya liberado puede resultar en comportamientos no definidos.

Un ejemplo básico de asignación y redimensionado podría ser:

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

int main(void) {
    int *numeros = (int *)malloc(5 * sizeof(int));
    if (numeros == NULL) {
        return 1; 
    }

    // Inicialización de los valores
    for (int i = 0; i < 5; i++) {
        numeros[i] = i * 2;
    }

    // Redimensionar el bloque a 10 elementos
    int *temp = (int *)realloc(numeros, 10 * sizeof(int));
    if (temp == NULL) {
        // Liberar la memoria anterior si la redimension falló
        free(numeros);
        return 1;
    }
    numeros = temp;

    // Llenar los nuevos espacios
    for (int i = 5; i < 10; i++) {
        numeros[i] = i * 2;
    }

    // Uso de los datos
    for (int i = 0; i < 10; i++) {
        printf("%d ", numeros[i]);
    }

    free(numeros); 
    return 0;
}

En este ejemplo, se demuestra cómo realloc() puede ampliar la memoria asignada anteriormente sin perder los valores ya almacenados. Es habitual verificar cuidadosamente si la asignación o la redimensión devuelven NULL, ya que esto sirve para gestionar errores de forma segura antes de intentar escribir o leer los datos en memoria.

Cómo evitar fugas de memoria (mem leaks)

Las fugas de memoria surgen cuando se reserva espacio dinámico y no se libera antes de perder la referencia al bloque asignado. Dicho problema puede comprometer el rendimiento del sistema y, en casos extremos, empujar a la aplicación a quedarse sin recursos.

Una buena práctica consiste en mantener un registro claro de cada puntero devuelto por la asignación dinámica. Se sugiere verificar en qué parte del código se reserva memoria y, de manera explícita, planificar el momento concreto en el que se llamará a free() correspondiente.

Es frecuente originar una pérdida de memoria al sobrescribir un puntero que apunta a un bloque gerenciado sin haber liberado primero la dirección antigua. Para minimizar riesgos, conviene usar variables temporales o comprobar cuidadosamente cada ajuste de punteros. Asimismo, establecer punteros a NULL inmediatamente después de liberar los bloques puede evitar accesos indebidos.

A continuación se muestra un ejemplo donde ocurre una fuga de memoria por no liberar la reserva inicial:

#include <stdlib.h>

void ejemploLeaking() {
    int *datos = malloc(10 * sizeof(int));
    if (datos == NULL) {
        return;
    }
    // Se pierde la referencia original sin usar free()
    datos = malloc(20 * sizeof(int));
    // Se vuelve a asignar memoria; la anterior no se libera
}

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

Para corregirlo, se debe liberar el bloque anterior antes de sobrescribir el puntero o, cuando sea innecesario, reutilizar la memoria ya asignada. Un enfoque adecuado sería:

#include <stdlib.h>

void ejemploCorregido() {
    int *datos = malloc(10 * sizeof(int));
    if (datos == NULL) {
        return;
    }
    free(datos); 
    datos = malloc(20 * sizeof(int));
    // Ahora se libera el bloque previo antes de reservar el nuevo
}

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

Es recomendable añadir controles exhaustivos en secciones críticas del programa, de modo que cada salida posible del flujo (incluidos retornos anticipados por error) asegure la liberación de toda la memoria en uso. De este modo, se reduce notablemente la probabilidad de incurrir en fugas.

Punteros nulos y gestión de errores en asignación dinámica

Cuando se asigna memoria en C mediante funciones como malloc() o calloc(), es frecuente verificar si el resultado devuelto es un puntero que apunte a una sección válida de memoria o, en caso contrario, sea NULL. Un valor NULL indica que la asignación ha fallado y no debe usarse dicho puntero para operar sobre la memoria, pues se incurriría en comportamientos no definidos.

Comprobar si un puntero es NULL justo después de la asignación resulta útil para tomar medidas de gestión de errores. Por ejemplo, se puede imprimir un mensaje y retornar de la función o salir del programa en caso de que la memoria requerida no pueda reservarse. Esta estrategia previene la ejecución de bloques de código que dependan de los recursos no asignados:

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

int *crearArrayEnteros(size_t n) {
    int *p = malloc(n * sizeof(int));
    if (p == NULL) {
        fprintf(stderr, "Error: no se pudo asignar memoria\n");
        return NULL;
    }
    return p;
}

int main(void) {
    int *datos = crearArrayEnteros(10);
    if (datos == NULL) {
        return 1; 
    }
    // Se procede con el uso de 'datos'
    free(datos);
    return 0;
}

Resulta adecuado rechazar cualquier operación posterior sobre un puntero nulo, ya que esto impide lecturas o escrituras indebidas. Asimismo, es aconsejable fijar el puntero a NULL inmediatamente después de llamar a free() para mantener una referencia inequívoca de que la memoria ya no está disponible y thus evitar accesos accidentales. Esto facilita, a su vez, la localización de errores lógicos ya que cualquier intento de utilizar de nuevo la dirección dará lugar a una verificación explícita de NULL.

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.

Plan mensual

19.00 € /mes

Precio normal mensual: 19 €
47 % DE DESCUENTO

Plan anual

10.00 € /mes

Ahorras 108 € 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 el uso de malloc, calloc, realloc y free para la asignación dinámica de memoria.
  • Aplicar técnicas para evitar fugas de memoria en proyectos.
  • Reconocer y manejar punteros nulos para una gestión de errores efectiva.
  • Redimensionar bloques de memoria sin pérdida de datos.