Memory leak cases analysis

Introduction

In this article, I will demonstrate some typical scenarios that cause memory leak. By the end of each scenario, I will also put analysis and fix. If you are interested, you can take a quick test to see whether you can figure out the error immediately.  

Note: Next samples are tested with visual c++ 6.0, Different compilers or different versions may have different behaviors.

1. X is not Y

1: class A{ };

2: class B : public A {

3:     private: 

4:         std::string str; 

5:     public: B() { 

6:         str="Hello";

7:     } 

8: }; 

9: void LeakTest() { 

10:     A * pA = new B(); 

11:     if( pA ) 

12:         delete pA; 

13: } 

Comments:
(1)The destructor of B is not defined, so the compiler will automatically generate one, in which the destructor of std::string will be called for str defined in line 4, and the internal buffer that holds "Hello" will be freed.
(2)Compiler also generates a default destructor for class A, but it is not virtual.
ERROR:  pA is deleted in line 12, Because pA is a pointer of class A and the default destructor of class A is not virtual, only the destructor of class A will be called, thus  the internal buffer allocated by str will be leaked. 
fix: add virtual destructor for class A, virtual ~A(){}

2. X and Y doesn't Z

1: class A{

2: private:

3:     std::string str;

4: pubilc:

5:     A(){ str = "Hello";}

6: };

7: void LeakTest1(){

8:     A* pArray = new A[100];

9:     if( pArray )

10:         delete pArray;

11: }

comments:
With debug version of this sample, you will see a assert failure window because the pointer passed to CRT delete function are different from pArray, CRT function detected it and report the error.
With release version, the sample can ran, but it leaks memory. line 10 only delete the internal buffer of the first item of the array.
fix: change line 10 to: delete[] pArray;

3. Store pointers in container

class A{

    int a;

};

class MyContainer{

public:

std::map<long,A*> leakMap; 

~MyContainer(){

    std::map<long,A*>::iterator it;

    for(it=leakMap.begin();it!=leakMap.end();it++){

        if( *it != NULL){

            delete *it;

    }

}

};

void LeakTest() {

   MyContainer  con;

    A* pA1 = new A();

    A* pA2 = new A();

    con.leakMap[1000] = pA1; 

    con.leakMap[1000] = pA2; 

}

Sometimes it maybe necessory to store pointers in container. When you replace existing pointer in container with new pointer. make sure delete the old pointer.
error:  when the sample put pA2 into the container, 

4. Soemtimes reference counting makes the investigation difficult. 

void LeakTest( std::string& str ){ 

    std::string temp("Hello"); 

    str=temp;

 }

 void Caller() { 

    std::string* pStr = new std::string(); 

    LeakTest( pStr ); 

}

error: There are two leaks here, one is the "new std::string()", the other is the "std::sting  temp("Hello")";the seconds is not released in its destructor because it referenced by another std::string, which is the input parameter str. So when the tool comlains about the leak, and it looks like it should be freed in the destructor. You can think about reference couting.

5. COM

_bstr_t GetInfo( ){

    BSTR A = SysAllocString("Hello");

    _bstr_t B = A;

    return B;

}

error: _bstr_t B in this sample will create a copy of BST A, B did not attach to A, so A should be free by calling SysFreeString before leaving the function. 

6 void *

void ThreadFunc( void * p ) {

// Do business logic

if( p )
    delete p; // let us assume it must be deleted here

}
void LeakTest(){
    A* p = new A();
    _beginthread( ThreadFunc,(void*)p );
}

error: This one is straight forward. In function ThreadFunc(), p should be converted to real type A to be deleted.

Conclusion:

The error code may seems simple, but in real world especially in a big application and the code has a lot of business logic. The error showed in this article maybe not so obviously. More attention paid to such lessons, more problems could be prevented.