Dynamic memory system

Application programming very often deals with dynamic memory allocation mechanism. Application programmers should allocate memory from the heap and the same memory should be freed when it is no longer needed. This helps the operating system to share the demand of dynamic memory in the running system. It is essential for an application to utilize system memory resources at an optimum level. Normally we allocate memory using malloc() or calloc() and de-allocate memory using free().

What is memory leak

It is a common rule of dynamic memory management that memory chunks allocated must be freed properly. A situation may arise where user allocates some chunks of memory and when done using these chunks of memory misses to de-allocate them. Memory leak is a situation created by poor programming practice where a chunk of memory allocated in heap memory is not freed and remains as allocated till the end of the execution. Memory leak can be of few bytes to several megabytes or more. There can be a small leak but that piece of code might get executed several times. If this situation happens very often in an application/process, it should increase process heap usage and the current working set of the task gets increased. This situation can lead to the operating system running out of virtual memory and thus creating a thrashing situation.

Why memory leak happens?

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 execution path 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 the de-allocations are not taken care of properly. Thus memory leak situation arises in runtime. It creates a bad impression on the end user as they encounter slowing down of application performance and sometimes unresponsive user interface.

How to detect memory leak

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

We know that a program should call couple of malloc/calloc and same number of free calls. If the number of malloc/calloc calls are greater than number of free calls then we are sure a memory leakage happened. So the way to detect memory leak is to trace the calls. We can check two issues.

  1. Weather program has memory leak or not (using a simple using counter)
  2. If program has memory leak then where (need linked list to trace all calls)

Detect memory leak using counter

This approach is very simple. We take a global counter and initialize it to zero at the beginning. Now we modify the definition of malloc and free with our private functions. During allocation we allocate through malloc and after a success we increment this counter. Same way during free we free the chunk and decrement the counter.

A memory leak free program should call as much malloc as free at the end. So this counter will be zero at exit. If this counter is above zero then programs has memory leak and the value of the counter will tell the number of chunks that are not freed. my_malloc() is our custom allocator and my_free() is our custom deallocator. We have replaced the definition of malloc and free with these functions. So our application program will call these routines instead of original malloc and free of C library.

#include<stdio.h>
#include<stdlib.h> 

int mem_counter = 0;

void my_free(void * p)
{
  free(p);
  printf ("Free memory %p \n", p);
  printf ("Current leak counter %d\n", --mem_counter);
}

void * my_malloc(int size)
{
  void * ret;
  ret = malloc(size);
  if(ret) {
    printf ("Allocated memory @%p of size %d \n", ret, size);
    printf ("Current leak counter %d\n", ++mem_counter);
  }
  return ret;
}

#undef malloc
#define malloc(size) my_malloc(size)
#undef free
#define free(p) my_free(p)


/*user code starts here - We are detecting leak here*/

void some_task1(void)
{
  void * p;
  printf("Task 1\r\n");
  p = malloc(512);
  if(p)
    free(p);
}

void some_task2(void)
{
  void * p;
  printf("Task 2\r\n");
  p = malloc(256);

  /*free(p);*/ /* << memory leak added here*/
}

void some_task3(void)
{
  void * p;
  printf("Task 3\r\n");
  p = malloc(20);

  if(p)
    free(p); 
}

int main(int argc, char *argv[])
{
  printf("Memory leak detect using counter\r\n");
  some_task1();
  some_task2();
  some_task3();
  if (mem_counter > 0)
    printf("memory leak detected, %d of chunk(s) not freeded\n", mem_counter);
  return 0;
}
/*user code ends here*/

Detect memory leak using counter output

This main program has three tasks where we have some alloc and free calls. We have created a memory leak by commenting a line in some_task2() which is doing a free(). Our detect memory leak logic detects the leak.

Memory leak detect using counter
Task 1
Allocated memory @0x7fae5bc02ad0 of size 512 
Current leak counter 1
Free memory 0x7fae5bc02ad0 
Current leak counter 0
Task 2
Allocated memory @0x7fae5bc029d0 of size 256 
Current leak counter 1
Task 3
Allocated memory @0x7fae5bc00640 of size 20 
Current leak counter 2
Free memory 0x7fae5bc00640 
Current leak counter 1
memory leak detected, 1 of chunk(s) not freeded

Detect memory leak using list

Now we know our program has memory leaks. So the next step is to know the origin of this leak. Details like - Which source file, which function, and which line is causing the memory leak? To find out the code giving the memory leak we can examine our code and can find out the root of the leak. However, this is a small program and it is easy to examine. A program with several source file and several lines of code in each source file may not be easy to do a manual review. For this reason we have to develop a logic to trace all details of allocations and deallocations. 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.

Detect leak data structure


This program uses a data-structure type named as "mem_node_t". Program creates an array of this data structure and used this array a the list for storing the contexts.

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

} mem_node_t;

mem_node_t - 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;

Detect leak functions

We have these three utility functions to work with malloc() and free. There is a function to display the statistics of the array / list.

  • 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.

Detect memory leak using list source code

Like before we have replaced the definition of malloc and free with our custom routines. During allocation we are calling add_mem_node() with memory allocation size, filename, line number. These context will be added in the array and it will be marked as allocated. Free is doing the opposite here. It will find out the array element where it was allocated and it will mark as free.

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. show_mem_stat() will be called at the end to display the memory statistics. An ideal program with no memory leak will call as much malloc as same as free.

#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(= 0; i &lt; 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(= 0; i &lt; 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(= 0; i &lt; 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! ");
  }
  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("Detect memory leak using C\r\n");
  job1();
  job2();
  job3();
  show_mem_stat();
  return 0;
}
/*user code ends here*/

Detect memory leak using list output

We have commented the line with free() in job3() and we see our memory leak detect logic had successfully detected the leak.

Detect memory leak using C
Job 1
Job 2
Job 3

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

This is our program jobs without commenting free(). We see our memory leak detect logic has reported no leak detected.

Detect memory leak using C
Job 1
Job 2
Job 3

no memory leak detected!

About our authors: Team EQA

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

Further readings

Construct your own strcpy and strcat/implementation of strcpy, strcat function in C
strcpy, strcat implementation in c using pointers, strncpy, strcat implementation in c, strcmp, strcat implementation in c, strcat implementation in c, implementation of strcpy, strcat function in c

memmove vs memcpy how different? implement your own code?
memmove vs memcpy, memmove and memcpy difference understanding overlapping memory with diagram. Implement your own memmove memcpy. example source code

How exceptions are handled in C? (return check, exit to OS in fatal error, NULL check, setjmp-longjmp)
Describes how exceptions are handled in C. return check for success, exit to OS in case of fatal error, NULL pointer check, try-catch like exception handling with setjmp-longjmp

What is setjmp and longjmp in C? How to handle C++ like try-catch exceptions in C?
Describes C++ like try-catch exception handling in C with setjmp and longjmp. setjmp saves context and longjmp reverts context back in error with source code example

What is memory corruption and how to detect memory corruption?
memory corruption in c, how to check memory corruption, how to avoid memory corruption, how to debug, example

#