What is Memory Leak in C/C++? How can we avoid?

What is Memory Leak in C

In this tutorial, you will learn what is a memory leak and what is the causes of memory leaks in C/C++ programming. You will also leak to how to avoid memory leaks in C/C++ with programming examples.

What is Memory Leak?

A memory leak is a curse for software because software shows undefined behavior due to the memory leak. The memory leak occurs when programmers forget to deallocate the allocated memory.

Let’s see a program,

In the below program, the programmer forgets to free the allocated memory, it can cause a memory leak.

int main()
{
    char * pBuffer = malloc(sizeof(char));
    
    /* Do some work */
    /*Not freeing the allocated memory*/
    return 0;
}

 

 

What is meant by memory leaks?

A memory leak is like a slow poison for available memory space. It is a gradual loss of available memory when an application repeatedly fails to return the allocated memory that it has obtained for temporary use. As a result, the available memory for that application becomes exhausted and the application can no longer function.

So memory leak is a serious issue for an application that runs continuously (servers) because a very small memory leak can eventually cause the application to terminate.

 

Some reason of memory leak in C/C++:

Generally, a memory leak occurs in C/C++ programming when the pointer is lost its original allocated value. It becomes the cause of the memory leak because the allocated object becomes unreachable and cannot be deallocated. Let’s see some examples of memory leaks.

1.Assign the different values to the pointer before calling the delete expression. See the mentioned Example,

Ex -1:

// Dynamically allocated int with malloc()
int* ptr = (int*)malloc(sizeof int);


// Assign null to pointer before calling free function.
ptr = NULL; // memory leak

 

Ex -2:

// Dynamically allocated int with value 6
int* ptr = new int(6);

// Assign null to pointer befoer calling delete expression.
ptr = nullptr; // memory leak

 

2.An exception occurs before deleting the memory. Example,

void f()
{
   //Dynamically allocated in with value 27
   int* ptr = new int(27);

   g();      // may throw

   delete p; // okay if no exception

} // memory leak if g() throws exception

 

3. pointer goes out of scope.

Ex-1:

void f()
{
   //local pointer dead once control goes out of the scope.
   // Allocated memory remain blocked when pointer dead.
    int* p = malloc(sizeof int);

} // memory leak

 

Ex-2:

void f()
{
   //local pointer dead once control goes out of the scope.
   // Allocated memory remain blocked when pointer dead.
    int* p = new int(7);

} // memory leak

4. Working on raw pointers.

5.Not deleting the memory properly. I am discussing it in the below section.

 

How to avoid memory leak in C?

There are many tools available to detect memory leaks. But we can also avoid the memory leak to follow some programming tips. So let see the tips,

1. Every malloc or calloc should have a free function:

It is a golden rule to write the free function after each malloc (calloc) function. Suppose in an application you have required to create an array of characters to store some dynamic data. Because we know that in C programming we use the memory management function (malloc or calloc) to create a dynamic array.

It is a good habit to write the free function just after the malloc or calloc. It prevents the scenario when the developer forgets to write the free function.

int fun(int n)
{
    char *pInfoData = malloc (n *sizeof(char));
    free(pInfoData);

    return 0;
}

 

Now start to write the code between malloc and free function. Like the below expression.

int fun(int n)
{
    char *pInfoData = malloc (n *sizeof(char));

    /* Do some work */

    free(pInfoData);

    return 0;
}

 

Sometimes we have required allocated memory throughout the application, in that situation we have to write the free function in a handler that will invoke at the end of the application. And we have to write the free function just after writing the malloc function to avoid the chance to forget.

For example,

Suppose there is a callback function DeactivateHandler() that is invoked at the end of the application, so we have to write the free function in DeactivateHandler() just after writing the malloc. These techniques reduce the probability to forget to free the memory.

 

2. Avoid the orphaning memory location:

At the time of memory deallocation, we need to free the memory from child to parent which means a child will be free first. If we free the parent first, it can be a cause of memory leak.

For example,

In the below code, the pointer to context structure is freeing first. So the pointer that is pointing to space for the information data become orphan and it can be a cause of memory leak.

typedef struct
{
    void *pvDataInfo;
    
} sContext;

//Allocate the memory to pointer to context structure
sContext  *pvHandle = malloc(sizeof(sContext));

//Allocate the memory for Information data
pvHandle-> pvDataInfo  = malloc(SIZE_INFO_DATA);

free(pvHandle); // pvDataInfo  orphan

 

3. Create a counter to monitor allocated memory:

It is a good technique to prevent memory leaks. In this technique, we will create two global counters and initialize them with 0. In every successful allocation, we will increment the value of the counter1 (Allocate_Counter ) and after deallocating the memory we will increment the counter2 (Deallocate_Counter). At the end of the application, the value of both counters should be equal.

This method helps you to track the status of allocated memory. To implement this technique we need to create three customize functions, one for memory allocation, and second for memory deallocation, and the last one to check the memory leak.

static unsigned int Allocate_Counter  = 0;
static unsigned int Deallocate_Counter  = 0;


void *Memory_Allocate (size_t size)
{
    void *pvHandle = NULL;
    pvHandle = malloc(size);
    if (NULL != pvHandle)
    {
        ++Allocate_Counter;
    }
    else
    {
        //Log error
    }
    return (pvHandle);
}


void Memory_Deallocate (void *pvHandle)
{
    if(pvHandle != NULL)
    {
        free(pvHandle);
        ++Deallocate_Counter;
    }
}


int Check_Memory_Leak(void)
{
    int iRet = 0;
    if (Allocate_Counter != Deallocate_Counter)
    {
        //Log error
        iRet = Memory_Leak_Exception;
    }
    else
    {
        iRet = OK;
    }
    return iRet;
}

 

4. Do not work on the original pointer:

It is a good habit to work on a copy of the pointer, it preserves the address of allocating memory. If there is any accidental change occurred on the pointer, this technique helps you to get the actual address of allocating memory that is needed at the time of memory deallocation.

int *pBuffer = malloc ( sizeof(char) * n );

//Create copy of the pointer
int *pTmpBuffer = pBuffer;


// Do some work


free (pBuffer);

 

5. Write the proper comments:

I think it is a good habit to write comments in every section of the code. It always reminds you that what you did. It helps you if you read your code after some months or years.

 

How to avoid memory leaks in C++?

Like C programming, in C++ you also need to deallocate the dynamic memory. The objects created by the new operator persist until you do not deallocate them with the delete operator. The use of the wrong delete operator is also the main culprit of memory leaks in C++ programming.

In C++ programming you need to follow more extra steps including the above-mentioned steps (avoid memory in C) to avoid the memory leak. So let’s see some other extra steps to avoid memory leaks in C++.

1. Use the Smart pointers:

Instead of managing memory manually in C++, try to use smart pointers as much as applicable. Memory management becomes easy with the use of smart pointers. A smart pointer is a class designed to supervise the dynamically allocated memory. It ensures that the allocated memory will be released when the smart pointer object goes out of the scope.

In modern C++ programming (since C++11), the standard library includes smart pointers. C++11 has three types of smart pointers std::unique_ptr, std::shared_ptr and std::weak_ptr. These are used to ensure that programs are free of memory and resource leaks and are exception-safe.

The below example compares a raw pointer declaration to a smart pointer declaration.

void rawPointerUse()
{
    // Using a raw pointer -- not recommended.
    int* ptr1 = new int;

    /*

    Use ptr1...

    */

    // Don't forget to delete!
    delete ptr1;
}


void smartPointerUse()
{
    // Declare a smart pointer on stack and pass it the raw pointer.
    std::unique_ptr<int> ptr2(new int(5));

    /*

     Use ptr2...

    */

} // ptr2 is deleted automatically here.

 

You can see the example, a smart pointer is a class template that you declare on the stack, and initialize by using a raw pointer that points to a heap-allocated object. Because the smart pointer is declared on the stack, its destructor is invoked when the smart pointer goes out of scope and it will automatically free the allocated memory. You don’t need to worry about memory deallocation.

 

2. Virtual destructors:

Virtual destructors are useful when you need to potentially delete an instance of a derived class through a pointer to the base class. Let’s understand why a virtual destructor is needed.

The following example code creates a polymorphic pointer basePtr whose static type is Base * and dynamic type is Derived *. When basePtr is deleted, it results in undefined behavior because Base does not have a virtual destructor.

#include <iostream>
using namespace std;

//Base class
class Base
{
public:
    Base()
    {
        cout<<"Base Constructor called\n";
    }
    ~Base()
    {
        cout<<"Base Destructor called\n";
    }
};

//Derived class
class Derived :public Base
{
    int *ptr;

public:
    Derived()
    {
        cout<<"Derived Constructor called\n";
        ptr = new int;
    }
    ~Derived()
    {
        cout<<"Derived Destructor called\n";
        delete ptr;
    }
};

int main()
{
    Base *basePtr = new Derived();

    // your code

    delete basePtr;

    return 0;
}

Output:

Behaviour Undefined.

 

Remark: Do not delete an object of derived class type through a pointer to its base class type that has a non-virtual destructor. According to C++ standard that “if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined”.

 

Now explicitly declare virtual destructor in Base class. The code will show defined behavior because by declaring the virtual destructor in the Base class, the vtable will be used to look up the actual destructor that must be called, based on the type of the object. So Both Base class and Derived class destructors will be called.

#include <iostream>
using namespace std;

//Base class
class Base
{
public:
    Base()
    {
        cout<<"Base Constructor called\n";
    }
    virtual ~Base() //virtual destructor
    {
        cout<<"Base Destructor called\n";
    }
};

//Derived class
class Derived :public Base
{
    int *ptr;

public:
    Derived()
    {
        cout<<"Derived Constructor called\n";
        ptr = new int;
    }
    ~Derived()
    {
        cout<<"Derived Destructor called\n";
        delete ptr;
    }
};

int main()
{
    Base *basePtr = new Derived();

    // your code

    delete basePtr;

    return 0;
}

Output:

Base Constructor called
Derived Constructor called
Derived Destructor called
Base Destructor called

 

3. Use of proper delete:

You must always use delete or delete[] with every new or new[] operation in your C++ program, even if the memory will be cleaned up by the program termination.

You also need to remember that if you will use the delete in place of the delete[], the behavior will be undefined. See the below example.

void yourcode(int n)
{
  char* ptr = new int[n];
  // ...
  delete ptr;     // ← ERROR! Should be delete[] ptr!
}

 

4. Use std::string instead of char *:

You should use std::string instead of char * in C++. The std::string class manages its own memory internally, so you can copy, create, destroy them easily.

 

 

Recommended Articles for you:



3 comments

Leave a Reply

Your email address will not be published. Required fields are marked *