The nineteen types of memory leak

By Stephen Kellett
30 November, 2022

Memory leaks affect all computer programs, be they desktop applications, service applications or web services. For many trivial applications or applications with a very short application lifetime, the odd memory leak is often not of significant importance and will go unnoticed.

However, for larger applications that use lots of memory or that need to run for a long time, memory leaks are a serious problem. Examples are web servers, services and daemons.

If the program runs for any significant amount of time, the program must have no memory leaks. Otherwise, it will most likely fail due to memory exhaustion and/or memory fragmentation.

The ideal for any program is no memory leaks.

What is a memory leak?

A memory leak is when memory allocated for use for a task is not deallocated at the end of the task. Even though the memory is no longer needed, it is not available to satisfy future memory allocation requests. In most forms of memory leak the pointer to the original memory allocation has been lost, and the original memory allocation is classed as “unreachable”.

How does the pointer to the original memory allocation get lost? 

This happens when:

  • the variable holding the pointer goes out of scope.
    	if (isALeapYear(year))
    	{
    		char	*name;
    
    		name = new char [100];
    		if (name != NULL)
    		{
    			sprintf(name, "year %d is leap", year);
    			displayMessage(name);
    		}
    
    		// pointer falls out of scope
    	}
    
  • the pointer gets overwritten with a new value (a new pointer value, or NULL).
    	char	*name;
    
    	name = new char [100];
    	if (name != NULL)
    	{
    		sprintf(name, "workstation%d", id);
    		doWork(name);
    		name = NULL; // pointer overwrite
    	}
    
    	delete [] name;
    

What are the consequences of memory leaks?

  • Left unchecked, memory leaks will ultimately result in the failure of the application to function when requests to allocate memory fail.
  • Memory leaks often mask other problems, such as memory corruptions, buffer overruns and buffer underruns. When the leak is fixed these other problems often reveal themselves showing you that the application has more serious problems that also need to be addressed.
  • Memory leaks can also cause memory fragmentation, which can cause memory allocation performance problems and memory allocation failures.

Why should you fix memory leaks?

  • Fixing memory leaks often reveals buffer overruns, buffer underruns, memory corruptions, calling functions on deleted objects (which will lead to indeterminate behaviour), and multiple access to memory that was freed (incorrect memory access problems) – also known as memory use after free. Fixing these problems then results in an overall boost in application software quality, reliability and robustness.
  • Fixing memory leaks improves your application’s memory footprint, allowing it to run for longer without errors and failures of operation. This, in turn, leads to greater user/customer satisfaction.

One proposed solution to memory leaks is to use garbage collection. A garbage collector removes the responsibility of deallocating memory from the programmer. Well, that is the theory. The reality is that garbage-collected languages and technologies (.Net, C#, Java, Python, Ruby, etc.) can all suffer from memory leaks caused by the programmer forgetting to set references to objects to NULL when they have finished with them. Garbage collection simply changes the cause of the memory leak from forgetting to deallocate the memory to forgetting to reset the reference to NULL. Either way, the memory gets leaked. However, the fix for deterministic memory leaks is often much easier to determine, whereas for garbage-collected memory leaks identifying where to reset object references is not always so easy.

In this article, we are concerned with deterministic memory leaks – the type of memory leak you may get when using a language such as C, C++, Delphi, Fortran, etc. – non-garbage-collected allocations.

We will cover garbage-collected memory leaks in another article.

How to deallocate memory

You should always use the correct deallocator to deallocate memory allocated by an allocator.

Calling the wrong deallocator will not always result in a crash. The deallocator may simply return without deallocating the memory.

For C/C++ compilers always ensure you deallocate with the same version of the same compiler:

  • If you allocate with VS2010, deallocate with VS2010
  • If you allocate with VS2022, deallocate with VS2022
  • If you allocate with MingW, deallocate with MingW
  • If you allocate with C++ Builder, deallocate with C++ Builder

C allocators and deallocators

Aligned deallocators should be matched with aligned allocators.

malloc free
calloc free
realloc free
_expand free
_aligned_malloc _aligned_free
_aligned_calloc _aligned_free
_aligned_realloc _aligned_free
_aligned_offset_malloc _aligned_free
_aligned_offset_calloc _aligned_free
_aligned_offset_realloc _aligned_free

C++ allocators and deallocators

Aligned deallocators should be matched with aligned allocators.

Array allocations should be matched with array deallocations, and single object allocations should be matched with single object deallocations.

malloc free
calloc free
realloc free
_expand free
_aligned_malloc _aligned_free
_aligned_calloc _aligned_free
_aligned_realloc _aligned_free
_aligned_offset_malloc _aligned_free
_aligned_offset_calloc _aligned_free
_aligned_offset_realloc _aligned_free
operator new operator delete
operator new [] operator delete []

Delphi allocators and deallocators

GetMemory FreeMemory
AllocMemory FreeMemory
ReallocMemory FreeMemory
GetMem FreeMem
AllocMem FreeMem
ReallocMem FreeMem
NewInstance FreeInstance, SysFreeAndNil
Create Free, SysFreeAndNil

Fortran allocators and deallocators

ALLOCATE DEALLOCATE

Win32 allocators and deallocators

HeapAlloc HeapFree
HeapReAlloc HeapFree
LocaAlloc LocalFree
GlobalAlloc GlobalFree
VirtualAlloc VirtualFree
SysAllocString SysFreeString
CoTaskMemAlloc CoTaskMemFree
CoTaskMemRealloc CoTaskMemFree
NetApiBufferAllocate NetApiBufferFree
NetApiBufferReallocate NetApiBufferFree

Memory leaks fall into one of several categories.

We have identified 19 broad categories of memory leaks. Some of them are variations of other memory leaks, often with different program scope.

We are going to outline them all here so that you are aware of them. It is important to call out each type of memory leak because although you may be aware of the core concepts you may have forgotten to check your code for one of the variations.

We will also identify example solutions for each type of memory leak.

We will show many styles of error in this article. We are not advocating the use of any of these coding styles (we even include the dreaded goto in here). But we do know they exist in codebases, possibly even in your codebase. You may be working with a large legacy codebase with a lot of technical debt. As such, it makes sense to cover all the bases. If you think we’ve missed one, please let us know. You will also notice that we try to keep the examples as simple as possible.

  1. Leaked temporary workspace
  2. Leaked data member
  3. Leaked class static data member
  4. Leaked global memory
  5. Leaked static memory
  6. Lifetime memory leak
  7. Leaked worker object
  8. Incorrect array delete memory leak
  9. Virtual object memory leak
  10. Calling the wrong deallocator memory leak
  11. Incorrect copy operator
  12. Data overwrite
  13. Data overwrite with reallocate
  14. Implicit memory allocation
  15. Early return, forgetting to deallocate
  16. Continue, forgetting to deallocate
  17. Break, forgetting to deallocate
  18. Goto, forgetting to deallocate
  19. Exception handling, forgetting to deallocate

#1 Leaked temporary workspace.

This is memory that is allocated inside a function or class method and which is not deallocated before the function completes.

HANDLE createCommsHandle(DWORD	id)
{
	char	*name;
	HANDLE	handle = NULL;

	name = new char [100];
	if (name != NULL)
	{
		sprintf(name, "workstation%d", id);
		handle = createHandle(name);
	}

	return handle;
}

In the above function, the memory allocated for the name variable is not deallocated after the call to createHandle().

Solution:

HANDLE createCommsHandle(DWORD	id)
{
	char	*name;
	HANDLE	handle = NULL;

	name = new char [100];
	if (name != NULL)
	{
		sprintf(name, "workstation%d", id);
		handle = createHandle(name);

		delete [] name; // memory leak fix
	}

	return handle;
}

#2 Leaked data member

This is memory allocated for use by a class member but which is not deallocated before the class object is destroyed.

class commsHandle
{
public:
	commsHandle();

	~commsHandle();

	void setName(char	*p_name);

	void createCommsHandle();

private:
	HANDLE	handle;
	char	*name;
};

commsHandle::commsHandle()
{
	name = NULL;
	handle = NULL;
}

commsHandle::~commsHandle()
{
	if (handle != NULL)
		CloseHandle(handle);
}

void commsHandle::setName(char	*p_name)
{
	size_t	len;

	len = strlen(p_name);
	name = new char [len + 1];
	if (name != NULL)
	{
		strcpy(name, p_name);
	}
}

void commsHandle::createCommsHandle()
{
	HANDLE	handle = NULL;

	handle = createHandle(name);

	return handle;
}

The above class has two problems:
#1 the destructor does not deallocate the memory allocated in setName().
#2 setName() does not deallocate the memory allocated in setName() prior to allocating a new value for name.

Solution:

commsHandle::~commsHandle()
{
	if (handle != NULL)
		CloseHandle(handle);

	if (name != NULL)
		delete [] name; // memory leak fix

}

void commsHandle::setName(char	*p_name)
{
	if (name != NULL)
	{
		delete [] name; // memory leak fix
		name = NULL;
	}

	size_t	len;

	len = strlen(p_name);
	name = new char [len + 1];
	if (name != NULL)
	{
		strcpy(name, p_name);
	}
}

#3 Leaked class static data member

This memory is allocated for use only by many functions of a class but shared across all instances of that class.

class commsHandle
{
public:
	commsHandle();

	~commsHandle();

	static void setName(char	*p_name);

	void createCommsHandle();

private:
	HANDLE		handle;
	static char	*name;
};

commsHandle::commsHandle()
{
	handle = NULL;
}

commsHandle::~commsHandle()
{
	if (handle != NULL)
		CloseHandle(handle);
}

void commsHandle::setName(char	*p_name)
{
	size_t	len;

	len = strlen(p_name);
	name = new char [len + 1];
	if (name != NULL)
	{
		strcpy(name, p_name);
	}
}

void commsHandle::createCommsHandle()
{
	HANDLE	handle = NULL;

	handle = createHandle(name);

	return handle;
}

The above class has two problems:
#1 setName() does not deallocate the memory allocated in setName() prior to allocating a new value for name.
#2 there is no function to deallocate the memory allocated in setName() – it is not possible to call a function to clean up this memory allocation.

Solution:

class commsHandle
{
public:
	commsHandle();

	~commsHandle();

	static void flushName(); // memory leak fix

	static void setName(char	*p_name);

	void createCommsHandle();

private:
	HANDLE		handle;
	static char	*name;
};

void commsHandle::flushName() // memory leak fix
{
	if (name != NULL)
	{
		delete [] name;
		name = NULL;
	}
}

void commsHandle::setName(char	*p_name)
{
	flushName(); // memory leak fix

	size_t	len;

	len = strlen(p_name);
	name = new char [len + 1];
	if (name != NULL)
	{
		strcpy(name, p_name);
	}
}

With the above solution commsHandle::flushName() needs to be called during program shutdown (or at the end of main()).

#4 Leaked global memory

This memory is allocated for use by many functions and is not part of a class.

char *name;

void setName(char	*p_name)
{
	size_t	len;

	len = strlen(p_name);
	name = new char [len + 1];
	if (name != NULL)
	{
		strcpy(name, p_name);
	}
}

Solution:

char *name;


void flushName() // memory leak fix
{
	if (name != NULL)
	{
		delete [] name;
		name = NULL;
	}
}


void setName(char	*p_name)
{

	flushName(); // memory leak fix

	size_t	len;

	len = strlen(p_name);
	name = new char [len + 1];
	if (name != NULL)
	{
		strcpy(name, p_name);
	}
}

With the above solution flushName() needs to be called during program shutdown (or at the end of main()).

#5 Leaked static memory

This memory is allocated solely for use by the function in which it is declared.

void doWork()
{
	static	char	*workspace = NULL;

	if (workspace == NULL)
	{
		workspace = new char [1000];
	}

	...
}

This is a similar leak to #4 except the scope of the memory is restricted to the function (or enclosing scope if declared at a deeper nesting level). The main problem with this allocation style is that there is no easy way to deallocate the memory if the memory is intended to be allocated only on the first call to the function (detected by the if (workspace == NULL) comparison).

Solution:

The solution is to provide an allocation function and a deallocation function. The allocation function getWorkSpace() is called from doWork() and the deallocation function flushWorkSpace() is called during program shutdown or at the end of main().

char *getWorkSpace(); // forward reference

void doWork()
{

	char	*workspace;

	workspace = getWorkSpace();

	...
}


static char *theWorkspace = NULL;

char *getWorkSpace()
{
	if (workSpace == NULL)
		theWorkSpace = new char [1000];

	return theWorkSpace;
}

void flushWorkSpace() // memory leak fix
{
	delete [] theWorkSpace;
	theWorkSpace = NULL;
}


int main(int argc, char *argv[])
{
	...
	
	// end of program

	flushWorkSpace(); // memory leak fix
}

#6 Lifetime memory leak

Some programming styles allocate memory once early in the program’s lifetime and deliberately never deallocate the memory.

static char *longLife = NULL;

// getLongLife called from other parts of the program

char *getLongLife()
{
	return longLife();
}

int main(int  argc,
	 char *argv[])
{
	longLife = new char [1000];

	doWork();
}

This is a one-shot memory allocation intended to last the entire program lifetime because the program authors think the memory may be useful at any stage in the application, including deep inside the program shutdown sequence.

This may happen for some multi-threaded applications where the memory is shared between many threads and for whatever reason the program authors do not think it wise to clean the memory up. One example of this was the lazy allocation of thread-local data in earlier versions of Microsoft’s C runtime. The CRT made no attempt to clean up the memory when the program exited. More recent versions of Microsoft’s CRT have different behaviour.

Other reasons can be creating workspace for debugging tools injected into a program where the tool expects to try to report data as far into the program shut down as it can go. As such the tool will want its workspace available until the operating system pulls the rug out from under it (which in our experience, is pretty much what happens, if you can fool the OS into letting your DLL last past its DllMain when it would normally be closed you won’t get a second notification that your DLL is going to be killed).

In our experience, although it may be convenient to have a programming style where you can just allocate a whole-application-lifetime object and not-deallocate it this programming style hinders the use of memory debugging tools (by all vendors, not just ourselves) as this whole-application-lifetime object will always be reported as a leak (because it never gets deallocated). That in turn means your memory-leak fix team need to be aware of this object (these objects!) and ignore it. Depending upon your application this can be a waste of developer time.

Much better and tidier to deallocate all memory allocations in every circumstance you can make it happen.

#7 Leaked worker object

This is when memory is allocated by function X and then passed to function Y to do a job and function X does not clean up because it expects function Y to clean up. A common case of this is when a function on one thread creates a data object to pass to a function on another thread.

// code on Thread 1

void processData(DWORD	id,
		 DWORD	tag,
		 DWORD	value)
{
	workerData	*wd;

	wd = new workerData(id, tag, value);
	if (wd != NULL)
	{
		addWorkerToQueue(wd);
	}	
}

void addWorkerToQueue(workerData	*wd)
{
	CCriticalSection	lock(&sect, TRUE);

	dataItems.Add(wd);
}

// code on Thread 2

void processQueue()
{
	CCriticalSection	lock(&sect, TRUE);
	DWORD			i, n;

	n = dataItems.GetSize();
	for(i = 0; i < n; i++)
	{
		workerData *wd; 

		wd = dataItems.GetAt(i); 
		if (wd != NULL) 
		{
			wd->doWork();
		}
	}

	dataItems.RemoveAll();
}

Solution:

The solution is to delete the worker objects once they have been used to do their work.

void processQueue()
{
	CCriticalSection	lock(&sect, TRUE);
	DWORD			i, n;

	n = dataItems.GetSize();
	for(i = 0; i < n; i++) 
	{
		workerData *wd; 

		wd = dataItems.GetAt(i); 
		if (wd != NULL) 
		{ 
			wd->doWork();

			delete wd; // memory leak fix
		}
	}

	dataItems.RemoveAll();
}

#8 Incorrect array delete memory leak

With C++ you can allocate arrays of objects and deallocate arrays of objects. The array form of delete is specified using [] and the non-array form of delete is specified without using [].

Consider this class. It correctly manages the memory it allocates. But how you allocate multiple instances of this class matters.

class memObj
{
public:
	memObj();

	~memObj();

	... // other functions

private:
	char	*data;
};

memObj::memObj()
{
	data = new [1000];
}

memObj::~memObj()
{
	delete [] data;
}

Consider this object definition that allocates memory in its constructor and deallocates memory in its destructor. On the face of it this does not look like it could leak memory. Well, it can if you allocate it in arrays and deallocate the array incorrectly…

	memObj	*array;

	array = new [10] memObj();

	delete array;

The above code allocates an array of 10 memObj objects then deallocates the array. The space for the array is deallocated but the destructor for each of the 10 memObj objects is only called for the first object. The other nine do not have their destructors called which means that each of the nine memObj objects leaks the memory it holds.

Solution:

	memObj	*array;

	array = new [10] memObj();

	delete [] array; // memory leak fix

For intrinsic datatypes and simple objects that have no virtual functions and contain only intrinsic datatypes, there may be no real damage by deallocating an array of objects using delete. But that leaves the door open to a memory leak should someone modify the object definition to be more complex or to have virtual functions. Thus you should always deallocate arrays using delete [] (even for intrinsics in case someone modifies the datatype of the intrinsic from say, “int” to “complexNumber”).

#9 Virtual object memory leak

Virtual functions are used in C++ to provide implementations of a function for an object that is derived from another object. For example, an apple object would implement a different flavour() function than a pear object, both objects would be derived from a base class fruit. Objects that are derived from other objects must always have a virtual destructor in the base class. If the base class is not declared as virtual the destructors for the derived objects will not be called, resulting in memory leaks if those objects are meant to deallocate memory.

First, we need to define a base class and some derived classes.

class train
{
public:
	train()

	~train();
};

train::train()
{
	...
}

train::~train()
{
	...
}

class steamTrain : public train
{
public:
	steamTrain();

	~steamTrain();

	void addFuel(int quantity);

private:
	coal	*fuel;
	DWORD	amount;
};

steamTrain::steamTrain()
{
	amount = 100;
	fuel = NULL;
	addFuel(amount);
}

steamTrain::~steamTrain()
{
	delete [] fuel;
}

void steamTrain::addFuel(int quantity)
{
	amount += quantity;
	delete [] fuel;
	fuel = new [amount] coal;
}

class electricTrain : public train
{
public:
	electricTrain();

	~electricTrain();

	void powerOn();

	void powerOff();

private:
	pantograph *power;
};

electricTrain::electricTrain()
{
	power = NULL;
}

electricTrain::~electricTrain()
{
	delete power;
}

void electricTrain::powerOn()
{
	delete power;
	power = new pantograph();
}

void electricTrain::powerOff()
{
	delete power;
	power = NULL;
}

Now if we use these object definitions…

	train	*steam = new steamTrain();
	train	*electric = new electricTrain();

	...

	delete steam;
	delete electric;

When the above code is executed the destructor for class train is called for both objects electric and steam. But the destructors for class steamTrain and class electricTrain is not called. This results in a memory leak of the coal and pantograph objects. The reason the destructors are not called is because the destructor for class train was not declared virtual.

Solution:

class train
{
public:
	train()

	virtual ~train(); // memory leak fix
};

#10 Calling the wrong deallocator

A form of memory leak we’ve seen a few times is caused by the wrong deallocator being used for a given allocator.

char *ptr;

ptr = new char [100];
free(ptr);

HLOCAL loc;

loc = LocalAlloc(LMEM_FIXED, 1000:
GlobalFree(loc);

When you do this different things can happen depending on what the allocating function was and what the deallocating function is. Possible outcomes are:

  1. Memory gets deallocated successfully! This can happen – if the implementation of delete calls free() then some calls to delete will succeed if the memory was allocated by malloc(). We do not recommend doing this. This relies upon implementation-dependent details that may change with a future version of the compiler, or change between debug and release builds. This is also a serious impediment to writing portable code should you be writing for more than one operating system.
  2. Memory does not get deallocated. Program execution continues as normal and no damage is done to the program.
  3. Memory does not get deallocated. Program execution continues as normal but damage is done to heap data structures in the program. This damage may lead to “random” crashes in your application sometime later.
  4. Memory does not get deallocated. The program crashes. When this happens you have a strong indicator that something is wrong and you may well identify the calling of the wrong deallocator as the cause of the crash.

You should always deallocate using the documented deallocator and, if applicable, the correct form of array/single declaration (for new/delete and new []/delete []).

#11 Incorrect copy operator

This memory leak is caused by failing to deallocate the existing object’s data prior to copying the data from the other object.

class weeble
{
public:
	weeble();
	
	~weeble();
	
	operator=(const weeble &other);
	
	void flush();
	
private:
	DontFallDown	*data;
	DWORD			duration;
};

weeble::weeble()
{
	data = NULL;
	duration = 0;
}

weeble::operator=(const weeble &other)
{
	if (this != &other)
	{
		data = new DontFallDown(*data);
		duration = other.duration;
	}
	
	return *this;
}

weeble::~weeble()
{
	flush();
}

void weeble::flush()
{
	delete data;
	data = NULL;
}

Solution:

weeble::operator=(const weeble &other)
{
	if (this != &other)
	{
		flush(); // memory leak fix

		data = new DontFallDown(*data);
		duration = other.duration;
	}
	
	return *this;
}

#12 Data overwrite

This memory leak is caused by overwriting a pointer to allocated memory with either another valid pointer, or with a NULL pointer.

class weeble
{
public:
	weeble();
	
	~weeble();
	
	void flush();
	
	void init();
	
	void wobble();	
	
	void setDuration(DWORD	d);
	
private:
	DontFallDown	*data;
	DWORD			duration;
};

weeble::weeble()
{
	data = NULL;
	duration = 0;
}

weeble::~weeble()
{
	flush();
}

void weeble::flush()
{
	delete data;
	data = NULL;
}

void weeble::init()
{
	data = new DontFallDown();
}

void weeble::setDuration(DWORD	d)
{
	duration = d;
	if (d == 0)
		data = NULL;
}

void weeble::wobble()
{
	if (data != NULL)
	{
		data->startWobbling();
	}
	
	DWORD	i;
	
	i = 0;
	while(i < duration)
	{
		data->wobbleSomeMore();
	}
	
	data->stopWobbling();
}

Solution:

Rewrite setDuration() to flush the data rather than reset the pointer to NULL.

void weeble::setDuration(DWORD	d)
{
	duration = d;
	if (d == 0)
		flush(); // memory leak fix
}

 

#13 Data overwrite with reallocate

This is a variation of leak #12.

The reason this memory leak is mentioned is that it’s often assumed that calls to realloc() (or HeapReAlloc(), etc.) will not fail. Realloc can fail if there is no block of a suitable size. When that happens it returns a NULL pointer, overwriting the previous value. But a scan of the logic won’t show this error, you need to be aware of the failure mode behaviour. A similar problems exists for the _expand() C runtime function.

char *ptr;

ptr = (char *)malloc(1000);
if (ptr != NULL)
{
	memset(ptr, 0, 1000);
	
	ptr = (char *)realloc(ptr, veryLargeValue); // memory leak here if veryLargeValue is too large
	if (ptr != NULL)
	{
		memset(ptr, 2, veryLargeValue);
		free(ptr);
	}
}

Solution:

Rewrite so that the return value from realloc() goes to a new variable which is compared before assignment to the target variable.

char *ptr;

ptr = (char *)malloc(1000);
if (ptr != NULL)
{
	char *newptr;
	
	memset(ptr, 0, 1000);
	
	newptr = (char *)realloc(ptr, veryLargeValue); // memory allocation failure here if veryLargeValue is too large
	if (newptr != NULL)
	{
		ptr = newptr;
		
		memset(ptr, 2, veryLargeValue);
	}
	
	if (ptr != NULL)
	{
		free(ptr);
	}
}

#14 Implicit memory allocation

This memory leak is caused by a called function (often a C runtime function or a Win32 function) allocating memory and the caller failing to deallocate the memory. This often happens because the called function will only allocate memory in specific circumstances.

	DWORD	lastError = GetLastError();
	CString	errorMessage;
	LPTSTR	lpMsgBuf;

	errorMessage = _T("When trying to eat a donut we received this error code:\r\n");

	FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | 
					FORMAT_MESSAGE_FROM_SYSTEM | 
					FORMAT_MESSAGE_IGNORE_INSERTS,
					NULL,
					lastError,
					MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
					(LPTSTR) &lpMsgBuf,
					0,
					NULL);

	errorMessage += _T("\r\nWin32 Error:\r\n\r\n");
	errorMessage += _T("\"");
	errorMessage += lpMsgBuf;
	errorMessage.TrimRight();
	errorMessage += _T("\"");

	MessageBox(errorMessage, _T("Unable to eat donut"), MB_ICONEXCLAMATION);
	return;

Solution:

	DWORD	lastError = GetLastError();
	CString	errorMessage;
	LPTSTR	lpMsgBuf;

	errorMessage = _T("When trying to eat a donut we received this error code:\r\n");

	FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | 
					FORMAT_MESSAGE_FROM_SYSTEM | 
					FORMAT_MESSAGE_IGNORE_INSERTS,
					NULL,
					lastError,
					MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
					(LPTSTR) &lpMsgBuf,
					0,
					NULL);

	errorMessage += _T("\r\nWin32 Error:\r\n\r\n");
	errorMessage += _T("\"");
	errorMessage += lpMsgBuf;
	errorMessage.TrimRight();
	errorMessage += _T("\"");

	LocalFree(lpMsgBuf); // memory leak fix

	MessageBox(errorMessage, _T("Unable to eat donut"), MB_ICONEXCLAMATION);
	return;

Logic changes causing memory leaks

The next few types of memory leak are often caused by an existing function being edited and having new control flows added.

  • an input parameter could be checked and the function abandoned early
  • a variable read in a loop and the loop terminated early
  • a variable read in a loop and skipped to the next iteration in the loop
  • addition of exception handling
  • and, please don’t do this, use of goto

#15 Early return, forgetting to deallocate

This is a variation of leak 1. The code returns early, forgetting to clean up.

DWORD doWork(int seed,
             int stepSize)
{
	Blob*	b;
	DWORD	value = 0;
	
	b = new Blob();
	b->init();
	
	fillBlob(b, seed, stepSize);
	if (checkError(b))
		return value; // memory leak caused by this early return
		
	value = b->transform();
	
	delete b;
	
	return value;
}

#16 Continue, forgetting to deallocate

This is a variation of leak 1. The code loops back, forgetting to clean up.

DWORD genetest::doWork()
{
	std::vector				seeds;
	std::vector::iterator	iter;
	
	getSeeds(seeds);
	for(iter = seeds.begin(); iter != seeds.end(); iter++)
	{
		Germination*	g;
		
		g = new Germination();
		g->mutate(*iter);
		if (!g->valid())
			continue; // memory leak caused by continue
			
		add(g); // added to a datastore inside genetest class
	}
}

#17 break, forgetting to deallocate

This is a variation of leak 1. The code drops out of the loop, forgetting to clean up.

DWORD genetest::doWork()
{
	std::vector				seeds;
	std::vector::iterator	iter;
	
	getSeeds(seeds);
	for(iter = seeds.begin(); iter != seeds.end(); iter++)
	{
		Germination*	g;
		
		g = new Germination();
		g->mutate(*iter);
		if (!g->valid())
			break; // memory leak caused by break
			
		runTests(g);

  		delete g;
	}
}

#18 Goto, forgetting to deallocate

This is a variation of leak 1. The code jumps ahead, forgetting to clean up.

DWORD doWork(int seed,
             int stepSize)
{
	Blob*	b;
	DWORD	value = 0;
	
	b = new Blob();
	b->init();
	
	fillBlob(b, seed, stepSize);
	if (checkError(b))
		goto exit; // memory leak caused by this goto
		
	value = b->transform();
	
	delete b;
	
exit:
	return value;
}

#19 Exception handling, forgetting to deallocate

This is a variation of leak 1. The code throws an exception, forgetting to clean up.

DWORD doWork(int seed,
             int stepSize)
{
	Blob*	b;
	DWORD	value = 0;
	
	b = new Blob();
	b->init();
	
	fillBlob(b, seed, stepSize);
	if (checkError(b))
		throw std::invalid_argument( "error filling blob" ); // memory leak caused by this thrown exception
		
	value = b->transform();
	
	delete b;
	
	return value;
}

Memory Fragmentation

A related problem for long-running applications and applications that use lots of memory is memory fragmentation. Memory fragmentation can be caused by poor memory allocation strategies and also by small, seemingly inconsequential memory leaks.

Every leak found is a leak that should be cleaned up.

GDI leaks and handle Leaks

This article is specifically talking about memory leaks. However, GDI leaks and handle leaks are just as serious. Not every item in this article has a corresponding GDI leak or handle leak equivalent. However, some of the memory leaks shown do have corresponding handle leak equivalents. So if you are concerned about GDI leaks and/or handle leaks, please read this article and just think about handles rather than memory. Most of the examples will apply.

Finding Memory Leaks

Now that you know about the multiple types of memory leak and their causes, the next thing is to find memory leaks, GDI leaks and handle leaks in your software so that you can fix them.

The easiest way to do this is with our memory leak tool Memory Validator.

Fully functional, free for 30 days