A memory “leak” is a block of memory that a program allocates that is no longer referenced. (Technically, there’s no such thing as a memory leak. Memory doesn’t leak and can’t leak.) For example, when your program allocates memory, it assigns the block’s location to a pointer. A leak can occur if one of the following occurs:
• You assign a different value to that pointer.
• The pointer was a local variable and execution exited from the block.
If your program leaks a lot of memory, it can run out of memory. Even if it doesn’t run out of memory, your program’s memory footprint becomes larger. This increases the amount of paging that occurs as your program executes. Increased paging makes your program run slower.
Here are some of the circumstances in which memory leaks occur:
• Orphaned ownership—Your program creates memory but does not preserve the address so that it can deallocate it at a later time.
The following example makes this (extremely) obvious:
char *str;
for( i = 1; i <= 10; i++ )
{
str = (char *)malloc(10*i);
}
free( str );
In the loop, your program allocates a block of memory and assigns its address to str. However, each loop iteration overwrites the address of the previously created block. Because the address of the previously allocated block is lost, its memory can never be made available to your program.
• Concealed allocation—Creating a memory block is separate from using it.
Because all programs rely on libraries in some fashion, you must understand what responsibilities you have for allocating and managing memory. As an example, contrast the strcpy() and strdup() functions. Both do the same thing—they copy a string. However, the strdup() function uses the malloc() function to create the memory it needs, while the strcpy() function uses a buffer that your program creates.
In many cases, your program receives a handle from a library. This handle identifies a memory block that a library allocated. When you pass the handle back to the library, it knows which memory block contains the data you want to use or manipulate. There may be a considerable amount of memory associated with the handle, and deleting the handle without telling the library to deallocate the memory associated with the handle leaks memory.
• Changes in custody—The routine creating a memory block is not the routine that frees it. (This is related to concealed allocation.)
For example, routine 2 asks routine 1 to create a memory block. At a later time, routine 2 passes a reference to this memory to routine 3. Which of these blocks is responsible for freeing the block?
This type of problem is more difficult than other types of problems in that it is not clear when your program no longer needs the data. The only thing that seems to work consistently is reference counting. In other words, when routine 2 gets a memory block, it increments a counter. When it passes a pointer to routine 3, routine 3 also increments the counter. When routine 2 stops executing, it decrements the counter. If it is zero, the executing routine frees the memory. If it isn’t zero, another routine frees it at another time.
• Underwritten destructors—When a C++ object creates memory, it must have a destructor that frees it. No exceptions. This doesn’t mean that a block of memory cannot be allocated and used as a general buffer. It just means that when an object is destroyed, it needs to completely clean up after itself; that is, the program’s destructor must completely clean up its allocated memory.