Wednesday, July 23, 2008

Avoid Memory Leaks with Auto Pointers

Hey Guys, i havent wroten a post for a long time.
Today i want to talk about Auto Pointers. It is a basic but powerful design pattern to prevent memory leaks.
When we are writing c++ programs we generally allocate some memory from heap and use it during the lifetime of a block/function/thread/process.. In most cases a careless programming causes leaking the memory. In case of an Exception or return statement we should give the memory back to OS, if we no longer need that heap area.

Let me introduce an example:

bool leak(){
char* pBuffer = new char[ MAX_BUFFER_LEN ];
// control , initialize or do smth with buffer
..................................

int rc = function1( pBuffer );
if( OK != rc )
return false; //Leak Memory

try{
rc = function2(); //function2 may throw
}catch( ... ){
return false; //Leak Memory
}

//Use buffer more
................................

delete[] pBuffer;
return true;
}

Obviously above function leaks memory if it returns false because of unsatisfied conditions or exceptions. If you are lucky your program just leaks the memory, but if system runs out of memory then your process will crash.

One way to avoid this leak is putting the delete[] pBuffer; statement to every return path. But this is not the best way, programmers may forget to do this or later another programmer may want to change this function and he may not have enough knowledge about all functions in big projects. Someone else can easily put a new return statement without releasing the memory. Besides that, code will become longer, if every return path contains resource cleanup statements.
Look at this:

bool leak(){
char* pBuffer = new char[ MAX_BUFFER_LEN ];
// control , initialize or do smth with buffer
..................................

int rc = function1( pBuffer );
if( OK != rc ){
delete[] pBuffer; //give memory back
return false;
}
try{
rc = function2(); //function2 may throw
}catch( ... ){
delete[] pBuffer; //give memory back
return false;
}

//Use buffer more (Another programmer may add new statements which may introduce new leaks
................................

delete[] pBuffer;
return true;
}


Auto Pointers make all this process easy. They are smart pointers which provides automatic memory deallocation.

now i am writing the same example with auto pointer:

bool leak(){
AutoVectorPtr pBuffer ( new char[ MAX_BUFFER_LEN ] );
// control , initialize or do smth with buffer
..................................

int rc = function1( pBuffer );
if( OK != rc )
return false;

try{
rc = function2(); //function2 may throw
}catch( ... ){
return false;
}

//Use buffer more
................................

return true;
}

So there are no delete[] statements, code is cleaner and more understandable. Even some other programmers add new return statements memory will never leak.
What is AutoVectorPtr ? It is a template class which takes a pointer to its constructor and deletes it in its destructor. Since it is a local object created in the function scope, it is guaranteed that it will be destroyed ( delete[] will be executed ) when function goes out of scope whether it returns or throws ( thanks to stack unwinding! )

Simple AutoVectorPtr is :

template
class AutoVectorPtr {

T* _MyPtr;
public:
AutoVectorPtr( T* ptr ) : _MyPtr( ptr ){}
~AutoVectorPtr(){ delete[] _MyPtr; }

T* operator()(){ return _MyPtr; }
};

Above is a simple AutoVectorPtr class for giving an idea what it looks like. It should also contain copy constructor, operator= and some more.
I named it as AutoVectorPtr because it makes operations on array. If it holds single item like int, char or any class object, we can name it something like AutoPtr.

There are some libraries which provide this kind of smart pointers.
Here is the wiki link: http://en.wikipedia.org/wiki/Auto_ptr

2 comments:

Özgür Macit said...

Emre, is there a way to do this in ANSI C? I'm sick of calling free() for all pointers for which I allocated space manually and thinking of where should I call it for all of the pointers. Moreover, I'm sick of checking the result of malloc() call. I think I'd better use C++, but it's too late :)

emreknlk said...

Yes this problem has a solution. There is always a way to do something. I ll write a simple post for you..