To locate a memory leak, you would typically build your project where new/malloc and delete/free is rerouted to a routine that tracks all allocations and deallocations throughout your program. Note that this does not extend to third party libraries. At the end of the program, a report is generated with the allocations that remain. Something like the following:
// not $100% exact...
#define new DBG_NEW
#define DBG_NEW new (__FILE__, __LINE__)
struct alloc_entry {
alloc_entry* prev;
alloc_entry* next;
const char* file;
int line;
size_t size;
void* p;
};
alloc_entry* alloc_list = 0;
void* operator new( const char* file, int line, size_t sz ) {
void* ptr = malloc(sz);
simple_list::insert(alloc_list, alloc_entry{0, alloc_list, file, line, sz, ptr});
return ptr;
}
void operator delete(void* p) {
if ( p )
simple_list::remove_where(alloc_list, p);
}
void alloc_report() {
for ( alloc_entry* p = alloc_list; p; p = p->next )
fprintf(stderr, "%s@%d %i bytes\n", p->file, p->line, p->size);
}
int main() {
delete new int;
new int;
alloc_report();
}
Memory leaks are caused in many ways, but the most common cause is improper object lifetime management. Objects on the stack have a very strict FILO lifetime, and are guaranteed to be cleaned up when its scope expires. In contrast, objects allocated on the heap do not have a defined lifetime, and can be deallocated in any order. Here are a few examples:
void error(void* p) {
throw std::runtime_error("Test failure!");
}
int main(int, char*[]) {
try {
foobar* f = new foobar;
error( new int );
delete f;
} catch ( const std::exception& ex ) {
// new int does not get deleted
// foobar does not get deleted
}
}
To prevent memory leaks in the future, you must always keep in mind how long a dynamically allocated object is supposed to exist, who owns the object, who shares ownership with the object, and who is responsible for cleaning that object. Smart pointers help here, but aren’t absolutely necessary.