One of the most common anomalies when managing memory
explicitly is what is known as a “memory leak”. This situation
appears when a program obtains a portion of dynamic memory and the value of
the pointer returned by the system, by mistake, is lost. In that case, it is
no longer possible to invoke the free
function to liberate that
memory portion and therefore is reserved for the remaining of the
execution. As an example of a memory leak, let us analyze the following code
fragment.
char *string; string = (char *)malloc(100); string = NULL;
The first line declares a pointer to char. In the second
line a space of 100 bytes is reserved. The memory manager returns a pointer
to the beginning of that block and it is stored in the
string
variable. At this point, the address of this block is
not stored anywhere else. The following line assigns the value
NULL
to the same pointer. What happened with the address of the
memory just reserved? It has been lost and there is no way to recover it
because string
was the only copy of such value. As a
consequence, the memory portion will be kept as reserved for the rest of the
program execution. The memory leaked.
The main consequence of a memory leak is that the memory portion cannot be reused (it is lost). This is equivalent to a reduction in the amount of available memory for the program execution. The effects of a memory leak depend on the location in the code where they are produced. If a program has a single leak of a few bytes, the effect will very likely go unnoticed. However, if the memory loss is produced in a location in the code that is executed repeatedly, the effect can be catastrophic. Consider the following code fragment:
#define MILLION 1000000 char *table[MILLION]; for (i = 0; i < MILLION; i++) { table[i] = (char *)malloc(100); table[i] = NULL; }
The memory leaks in a location that is part of a loop executed one million times. Thus, in the loop, almost 100 megabytes of memory are leaked.
Memory leaks typically happen in situations much more difficult to detect than the trivial ones shown here. They are typically in unexpected locations in the code and are due to errors when manipulating pointers. The memory leak problem in C is so complex that several commercial as well as open source tools have appeared specially conceived to analyze a program and detect them.
A typical memory leak situation is when manipulating chained
data structures. In a data structure several pointers obtained through
malloc
are stored, and inside their memory, other pointers
identically obtained are stored. The deallocation of the memory occupied by
these data structures must be programmed with extreme care. The following
code fragment illustrates this problem.
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);
Variable agenda
is reserved with space to store
100 structures of type struct contact_information
. In the loop,
the two first fields of each structure are initialized with two pointers
obtained through malloc
. After the loop the call
free(agenda)
deallocates the space reserved for the table, but
not the one reserved for the strings in each of its elements. The correct
way to free the structure is also looping through all its elements and
deallocated each field separately with its own call to free
.
The two rules to observe for any C program related to dynamic memory management are:
Any memory portion dynamically reserved (with
malloc
, calloc
or realloc
) must
be deallocated through a call to free
.
If a program executes its last instruction and has dynamic memory blocks not deallocated, the program is considered incorrect.
Unfortunately, there no concrete technique to avoid memory leaks, but there are tools that given a program, analyze the execution and produce a report with the memory that leaked (if any). To give you an idea of the difficulty of this problem, when these tools started to appear, they were used to analyze applications that were considered solid and mature. Surprisingly, several memory leaks were detected and they were not noticed by any programmer in the development team.