A memory leak is some allocation of memory which is not deallocated once it is no longer needed. The term describes the situation in which memory dribbles away and is lost, not available to be used again. Memory leaks cause a program to appear to use more memory over time than they should. Severe leaks may cause a program to run out of memory. This is especially trouble-some for a program like a parallelizing compiler which tends to use much memory, especially when compiling large programs.
A memory leak can be caused by subtle nuances of the C++ language, but leaks can be obvious when you know what you are looking for. Take, for instance, the following code fragment:
{ RangeAccessor loop_ranges( *_ranges, s_iter.current() ); Expression *range = loop_range( s_iter.current() ); Expression *limit = constant( switch_value("processors") ); // Compare the # of iterations with the # of processors Relation bounds_rel = loop_ranges.compare(*range, *limit); . . . use bounds_rel to set variables in outer scope . . . }
C++ will automatically delete the whole objects that it can see which are created in a given scope, for instance the RangeAccessor, two Expression * variables and the Relation above. But the Expressions themselves (which are created in the routines loop_range and constant above) are not automatically deleted. Since the pointers to them do get deleted at the end of the block, there is no longer a way to access them and they can never be either used or deleted again.
In an attempt to plug this memory leak, a person may write the following code:
{ RangeAccessor loop_ranges( *_ranges, s_iter.current() ); Expression *range = loop_range( s_iter.current() ); Expression *limit = constant( switch_value("processors") ); // Compare the # of iterations with the # of processors Relation bounds_rel = loop_ranges.compare(*range, *limit); delete range, limit; . . . use bounds_rel to set variables in outer scope . . . }
Unfortunately, this does not totally fix the problem.
The Expression pointed to by range survives this fragment for the simple reason that a comma-expression in C++ evaluates to the value of the last element of the comma-separated list. So, `` delete range, limit;'' has the same effect as `` delete limit;''. This is still a memory leak.
The following is a code fragment without leaks:
{ RangeAccessor loop_ranges( *_ranges, s_iter.current() ); Expression *range = loop_range( s_iter.current() ); Expression *limit = constant( switch_value("processors") ); // Compare the # of iterations with the # of processors Relation bounds_rel = loop_ranges.compare(*range, *limit); delete range; delete limit; . . . use bounds_rel to set variables in outer scope . . . }
Within Polaris, we have replaced the global new and delete functions with versions which keep track of things which have been created and deleted. If you want to find out whether a memory leak occurs in a given block of code, you can bracket the code block with calls to HeapStats::reset() and HeapStats::report();. For instance:
HeapStats:reset(); Statement *foo = new AssignmentStmt(...) <Creation of other objects on the heap> ... delete foo; <Deletion of other objects on the heap> HeapStats::report();
It is important to bracket an appropriate region. If something is allocated inside the region and deallocated outside it, it will appear (falsely) to be a memory leak.
The call to HeapStats::report() will print the number of calls to new and the number of calls to delete since the call to HeapStats:reset(). Assuming that the bracketed region is chosen appropriately, the following would be true:
Once you have determined that a memory leak is occuring in a block, you can use gdb to find it. This can be tedious, but is not difficult. Once you have placed the calls to HeapStats::reset() and HeapStats::report() around the block of code and found that a leak occurs in that block, you enter gdb and set a breakpoint at both the reset() and the report() calls. Run the code until you reach the reset() call, then type:
rbreak _builtin_new rbreak _builtin_delete
These commands will cause gdb to halt at every new or delete call intercepted by HeapStats. At each break, make a note of which object is being created or deleted (you may need to use the backtrace command and one or more up commands to determine exactly the object involved). When execution reaches the HeapStats::report() call, any object which has been created but not deleted is a memory leak.