Application programming very often deals with dynamic memory allocation mechanism. It is essential for an application to utilize system memory resources at an optimum level. Normally we allocate memory using malloc()/calloc() and de-allocate memory using free().

It is a common rule of dynamic memory management that memory chunks allocated must be freed properly. Now what is this memory leak?

A situation may arise where user allocates some chunks of memory and when done using these chucks of memory misses to de-allocate them. This is a small granule of memory leak. If this situation happens very often in an application/process, it increases heap usage and current working set of the task gets increased. This situation can lead to the operating system running out of virtual memory and thus creating thrashing situation.

In normal situation, a good programming practice always ensures proper de-allocation of memory. Good designing and code implementation always takes care of this.

Our program flow normally follows a good path/execution and we almost never encounter memory leaks. But sometimes incorrect logic or error path may be present which may not include proper de-allocation code. This happens because of rapid development or not taking care of all error situations properly during coding or designing. Some error situations can take these paths where we have not taken care of the de-allocation properly. Thus comes memory leak. It creates a bad impression on the end user as they encounter slowing down of application performance and sometimes unresponsive user interface.

Now that we know memory leaks can happen, how to debug this? How to trace which portion of the code or which execution path is causing this problem?

We have the following example where we will try to understand this situation. Here we have 3 jobs. Job is a logical program module/block where at the beginning we allocate some memory and we use the memory in the execution path. Then at the end when we are done we deallocate the memory. In practical situations, job can be a worker thread or a computation unit where user deals with some defined work.

To track and monitor allocation and deallocation we have created an array of context data-structure named as mem_node[]. We save each malloc context inside this array as one array element.

How this program works:
This program uses a data-structure type named as mem_node_t and it has an array of this type.

Each member of this data-structure holds some information of the malloc call. The members are like -

  • mem_pointer - pointer allocated by malloc()/calloc();
  • file_name - source file name where allocation hapens;
  • function - function name where allocation hapens;
  • line - line number where allocation hapens;
  • size - size of the allocation;
  • flag - this flag tracks if this object is in use;

Functions:

  • add_mem_node - It allocates memory using malloc()/calloc() and saves the context to the one element of mem_node array and marks flag as in use.
  • del_mem_node - It searches for the allocation context in mem_node array and if found marks this context as free(setting flag = 0) and also frees the memory using free().
  • show_mem_stat - Iterates through all the allocated elements of mem_node array and displays those elements which have not been freed.

Sample output and source code:

====================================================

mem leak test
Job 1
Job 2
Job 3

0x003E24A0 of 10 bytes allocated from main.c:115 in job3() is not freed

=====================================================

mem leak test
Job 1
Job 2
Job 3

no memory leak detected!

Source code:

#include <stdio.h>
#include <malloc.h>


typedef struct _mem_node_t
{
  void * mem_pointer;
  char * file_name;
  char * function;
  int line;
  int size;
  int flag;

} mem_node_t ;

#define MAX_MEM_PTRS 1024
mem_node_t mem_node[MAX_MEM_PTRS];

void *add_mem_node(int size,
                   char * file_name,
                   int line,
                   char * function)
{

  int i = 0;
  int ret = -1;

  void *pointer = malloc(size);

  for(i = 0; i < MAX_MEM_PTRS; i++) {
      if(mem_node[i].flag == 0) {
        mem_node[i].flag = 1;
        mem_node[i].mem_pointer = pointer;
        mem_node[i].file_name = file_name;
        mem_node[i].function = function;
        mem_node[i].line = line;
        mem_node[i].size = size;
        ret = 0;
        break;
      }
  }
  return pointer;
}

void del_mem_node(void * mem_pointer)
{

  int i = 0;
  int ret = -1;

  for(i = 0; i < MAX_MEM_PTRS; i++) {
      if(mem_node[i].mem_pointer == mem_pointer) {
        mem_node[i].flag = 0;
        mem_node[i].mem_pointer = NULL;
        mem_node[i].file_name = 0;
        mem_node[i].function = 0;
        mem_node[i].line = 0;
        mem_node[i].size = 0;
        ret = 0;
        free(mem_pointer);
        break;
      }
  }
  return;
}



void show_mem_stat(void)
{
  int i = 0;
  int ret = 0;

  for(i = 0; i < MAX_MEM_PTRS; i++) {
      if(mem_node[i].flag == 1) {
        printf("0x%p of %d bytes allocated"
               " from %s:%d in %s() is not freed\r\n",
               mem_node[i].mem_pointer,
               mem_node[i].size,
               mem_node[i].file_name,
               mem_node[i].line,
               mem_node[i].function);
        ret = 1;
      }
  }
  if(ret == 0) {
     printf("no memory leak detected!\r\n");
  }
  return;
}

#undef malloc
#define malloc(size) add_mem_node(size, \
                                  __FILE__, \
                                  __LINE__, \
                                  (char*)__FUNCTION__)
#undef free
#define free(_p) del_mem_node(_p)

/*user code starts here - We are detecting leak here*/
/*Please note we do not need to modify malloc/free calls*/

void job1(void)
{
  void * p = malloc(1024);
  printf("Job 1\r\n");
  free(p);
}

void job2(void)
{
  void * p = malloc(100);
  printf("Job 2\r\n");
  free(p);
}

void job3(void)
{
  void * p = malloc(10);
  printf("Job 3\r\n");
  /*free(p);*/ /* << memory leak added here*/
}

int main(int argc, char *argv[])
{
  printf("mem leak test\r\n");
  job1();
  job2();
  job3();
  show_mem_stat();
  return 0;
}
/*user code ends here*/

You have viewed 1 page out of 248. Your C learning is 0.00% complete. Login to check your learning progress.

 Vote 0

Similar topics related to this section

macro definition, C exception handling, setjmp and longjmp, trace a memory leak, detect memory corruption, Big Endian and Little Endian, stack grows up or down,

# C Programming Language (Prentice Hall Software)
# Let Us C Paperback - 2006 by Yashavant Kanetkar
# Understanding and Using C Pointers Core techniques for memory management
# Data Structures Using C and C++ Paperback - 1998
# Data Structures In C Paperback - August 11, 2008 by Noel Kalicharan