UC3M

Grado en Ing. Telemática/Sist. Audiovisuales/Sist. de Comunicaciones

Arquitectura de Sistemas

Septiembre 2017 - Enero 2018

6.10.2. La fuga de memoria

Una de las anomalías más comunes cuando se gestiona la memoria de forma explícita es lo que se conoce como fuga de memoria. Esta situación ocurre cuando un programa obtiene memoria dinámica, y el valor del puntero que devuelve el sistema, por error, se pierde. En tal caso, ya no es posible invocar a la función free con ese puntero, y la porción de memoria se queda reservada por lo que resta de ejecución. Como ejemplo de fuga de memoria analicemos el siguiente fragmento de código.

char *string;
  string = (char *)malloc(100);
  string = NULL;

La primera línea declara un puntero a carácter. En la segunda se reserva un espacio de 100 bytes. El gestor de memoria devuelve un puntero al comienzo de ese bloque y se almacena en la variable string. En ese momento, la dirección de ese bloque no está almacenada en ningún otro sitio. La línea siguiente asigna el valor NULL al mismo puntero. ¿Qué ha sucedido con la dirección de memoria de la porción que se acaba de reservar? Se ha perdido y no hay forma alguna de recuperarla, porque string era la única copia de ese valor. Como consecuencia, la porción de memoria reservada seguirá marcada como ocupada por el resto de ejecución del programa. La memoria se ha fugado.

La principal consecuencia de una fuga de memoria, por tanto, es que esa porción no se puede utilizar, se ha perdido. Esto es equivalente a que la memoria disponible para la ejecución del programa se haya reducido. Los efectos de una fuga de memoria dependen del lugar en el código en el que se produzca. Si en un programa se fuga una única porción de unos cuantos bytes, es posible que su efecto pase desapercibido. Sin embargo, si la pérdida de memoria se produce en un lugar que se ejecuta un número muy elevado de veces, el efecto puede ser mucho más notorio. Fíjate en el siguiente fragmento de programa:

#define MILLION 1000000

char *table[MILLION];
for (i = 0; i < MILLION; i++) {
  table[i] = (char *)malloc(100);
  table[i] = NULL;
}

La fuga de memoria se produce en un lugar que forma parte de un bucle que se ejecuta un millón de veces. En este bucle se fugan casi 100 Megabytes de memoria.

Las fugas de memoria no se producen en situaciones tan obvias como las descritas anteriormente, sino que aparecen en lugares del código inesperados debidos a despistes en la manipulación de punteros. El problema de las fugas de memoria en C es tan complicado de solventar que han aparecido herramientas especializadas, tanto comerciales como de código libre, especialmente concebidas para analizar un programa y detectar fugas.

Una situación típica de fuga de memoria es cuando se manipulan estructuras de datos encadenadas. En una estructura se almacenan punteros obtenidos mediante llamadas a malloc y en ellos a su vez se almacenan más punteros obtenidos de esta manera. La liberación de la memoria que ocupan estas estructuras de datos ha de programarse con sumo cuidado. El siguiente fragmento de código ilustra este problema.

struct contact_information 
{
  char *name, *lastname;
  int age;
};

struct contact_information *agenda;
int i;

agenda = (struct contact_information *)calloc(100, sizeof(struct contact_information));
for (i = 0; i < 100; i++) {
  agenda[i].name = (char *)malloc(10);
  agenda[i].lastname = (char *)malloc(30);
  agenda.age = 0;
}
free(agenda);

La variable agenda se reserva con espacio suficiente para almacenar 100 estructuras del tipo struct contact_information. En el bucle, los dos primeros campos de cada una de las estructuras se inicializa con dos punteros que se obtienen mediante malloc. Al terminar el bucle, la llamada free(agenda) libera el espacio reservado para la tabla, pero no el que se ha reservado para las cadenas de texto de cada uno de sus elementos. La forma correcta de liberar la estructura es igualmente con un bucle que atraviese la tabla y libere cada campo por separado con una llamada a free.

Las dos reglas a respetar en cualquier programa en C en lo referente a la gestión dinámica de memoria son:

  1. Toda porción reservada de forma dinámica (con malloc, calloc o realloc) debe ser liberada mediante una llamada a free.

  2. Si un programa llega a su última instrucción y tiene bloques de memoria dinámica sin liberar, se considera que el programa es erróneo.

Desafortunadamente, no hay una técnica concreta para evitar las fugas de memoria, pero sí hay herramientas que dado un programa lo analizan para ofrecerte un informe sobre qué memoria se ha fugado (si ha habido alguna). Para darte una idea de la dificultad de este problema, cuando las primeras herramientas de detección de fugas aparecieron, se utilizaron para analizar aplicaciones que se consideraban sólidas y maduras, y para sorpresa de sus diseñadores, se detectaron fugas que hasta el momento ningún programador había detectado.