TheAlgorithms-C/developer_tools/malloc_dbg.c
tinouduart33 3682694f76
feat: Get a trace of all of the allocations, using a doubly linked list and wrappers for malloc/calloc and free. (#782)
* Add malloc, calloc and free wrappers to trace all of the allocations

* Improve memory leak reporting when multiple allocations occurs in the same file at the same line.

* Change directory name from 'debugging' to 'developer_tools' and added CMakeLists.txt, also change an include name to match a file name

* Edit root CMakeLists.Txt

* Update developer_tools/malloc_dbg.h

Co-authored-by: David Leal <halfpacho@gmail.com>

* Edit CMakeLists.txt to create a static library from malloc_dbg.c

* Add comments for the includes

* Change test.c name to test_malloc_dbg.c, also edit CMakeLists.txt to link malloc_dbg.a only to test_malloc_dbg.

* Change comment style and change EXIT_SUCCESS to 0

* Fix typo in doxygen comments

* Enhance comments

* Apply suggestions from code review

Co-authored-by: David Leal <halfpacho@gmail.com>
2021-02-17 16:38:41 -06:00

290 lines
8.5 KiB
C

/**
* @file
* @brief This file contains malloc_dbg, calloc_dbg, free_dbg and printLeaks implementations.
* @author [tinouduart33](https://github.com/tinouduart33)
*/
#include <stdlib.h> /// For the malloc, calloc and free functions.
#include <stdio.h> /// For IO operations (printf).
#include <string.h> /// For the memcmp function.
#include "malloc_dbg.h" /// Header file which contains the prototypes of malloc_dbg, calloc_dbg and fre_dbg.
/* We must undef these macros in order to use the real malloc / calloc and free functions */
#undef malloc
#undef calloc
#undef free
/**
* @brief Structure used to save an allocated pointer
*/
typedef struct MEMORY_INFORMATION
{
void* ptr; ///< Pointer returned by malloc / calloc
const char* fileName; ///< File in which malloc or calloc has been called
const char* functionName; ///< Function in which malloc or calloc has been called
size_t bytes; ///< Number of bytes allocated
int line; ///< Line number (in file) corresponding to the malloc / calloc call
struct MEMORY_INFORMATION* next; ///< Next element in the list
struct MEMORY_INFORMATION* previous; ///< Previous element in the list
} mem_info;
/** We use a global variable for the list so we can use it at any time.
* */
mem_info* memoryInformation = NULL;
/** Another global variable. This one is used to know if we already call the atexit function.
* */
int atexitCalled = 0;
/**
* @brief addMemInfo function add a memory allocation in the memoryInfo list.
* @details This function creates a new element and add it on top of the list
* @param memoryInfo Pointer to the doubly linked list used to store all of the allocations
* @param ptrToreturn Pointer returned by malloc or calloc
* @param bytes Size in bytes of the allocation
* @param line Line where the allocation has been called
* @param filename File where the allocation has been called
* @param functionName Name of the function where the allocation has been called
* @returns mem_info pointer if it succeeds, NULL otherwise
*/
mem_info* addMemInfo(mem_info* memoryInfo, void* ptrToReturn, size_t bytes, int line, const char* filename, const char* functionName)
{
mem_info* newMemInfo = (mem_info*)malloc(sizeof(mem_info));
if (!newMemInfo)
{
return NULL;
}
newMemInfo->ptr = ptrToReturn;
newMemInfo->bytes = bytes;
newMemInfo->line = line;
newMemInfo->fileName = filename;
newMemInfo->functionName = functionName;
newMemInfo->next = memoryInfo;
newMemInfo->previous = NULL;
if (memoryInformation)
memoryInformation->previous = newMemInfo;
return newMemInfo;
}
/**
* @brief inList function is used to know if an element is already in the memoryInfo list.
* @details This function is used to know if an allocation in a specific file at a specific line already exists in the list.
* @param filename File in which malloc or calloc has been called
* @param line Line number in the file in which malloc or calloc has been called
* @returns Position of the element in the list if the element is found, -1 otherwise.
*/
int inList(const char* filename, int line)
{
mem_info* tmp = memoryInformation;
int counter = 0;
int len = strlen(filename);
while (tmp)
{
if (len == strlen(tmp->fileName))
{
if (!memcmp(filename, tmp->fileName, len) && tmp->line == line)
{
return counter;
}
}
tmp = tmp->next;
counter++;
}
return -1;
}
/**
* @brief editInfo function is used to edit an element in the memoryInfo list.
* @details This function is used to edit the number of bytes allocated at a specific line.
* @param elemPos Position of an element in the doubly linked list memoryInfo
* @param bytes Size of the allocation in bytes
* @returns Nothing.
*/
void editInfo(int elemPos, size_t bytes)
{
int counter = 0;
mem_info* tmp = memoryInformation;
while (counter != elemPos)
{
tmp = tmp->next;
counter++;
}
tmp->bytes += bytes;
}
/**
* @brief malloc_dbg function is a wrapper around the malloc function.
* @details This function calls malloc and allocates the number of bytes passed in the parameters.
* If the allocation succeeds then it add the pointer returned by malloc in the mem_info list.
* @param bytes Size of the allocation in bytes
* @param filename Caller file
* @param functionName Caller function
* @returns Pointer returned by malloc if it's valid, NULL otherwhise.
*/
void* malloc_dbg(size_t bytes, int line, const char* filename, const char* functionName)
{
void* ptrToReturn = malloc(bytes);
int pos = 0;
if (!ptrToReturn)
{
return NULL;
}
// We must check atexitCalled value to know if we already called the function
if (!atexitCalled)
{
atexit(printLeaks); // Used to call printLeaks when the program exit
atexitCalled = 1;
}
pos = inList(filename, line);
if (pos == -1)
{
// Add a new element in the mem_info list
memoryInformation = addMemInfo(memoryInformation, ptrToReturn, bytes, line, filename, functionName);
if (!memoryInformation)
{
free(ptrToReturn);
return NULL;
}
}
else
{
editInfo(pos, bytes);
}
return ptrToReturn;
}
/**
* @brief calloc_dbg function is a wrapper around the calloc function.
* @details This function calls calloc and allocates the number of bytes passed in the parameters.
* If the allocation succeeds then it add the pointer returned by malloc in the mem_info list.
* @param elementCount number of element to allocate
* @param elementSize Size of each element
* @param line Line number in the caller file
* @param filename Caller file
* @param functionName Caller function
* @returns Pointer returned by calloc if it's valid, NULL otherwhise.
*/
void* calloc_dbg(size_t elementCount, size_t elementSize, int line, const char* filename, const char* functionName)
{
void* ptrToReturn = calloc(elementCount, elementSize);
if (!ptrToReturn)
{
return NULL;
}
// We must check atexitCalled value to know if we already called the function
if (!atexitCalled)
{
atexit(printLeaks); // Used to call printLeaks when the program exit
atexitCalled = 1;
}
// Add a new element in the mem_info list
memoryInformation = addMemInfo(memoryInformation, ptrToReturn, elementCount * elementSize, line, filename, functionName);
if (!memoryInformation)
{
free(ptrToReturn);
return NULL;
}
return ptrToReturn;
}
/**
* @brief free_dbg function is used to free the memory allocated to a pointer.
* @details This function free the memory pointed by the pointer passed in parameter.
* To free this pointer, we loop through the mem_info list and check if we find the pointer.
* Once it's found, the pointer is freed and the element is deleted from the list.
* @param ptrToFree Pointer that must be freed
* @returns Nothing.
*/
void free_dbg(void* ptrToFree)
{
mem_info* tmp = memoryInformation;
mem_info* toFree = NULL;
mem_info* previous = NULL;
// Check if the head contains the pointer to free
if (tmp->ptr == ptrToFree)
{
toFree = tmp;
memoryInformation = tmp->next;
free(toFree->ptr);
free(toFree);
if (memoryInformation)
{
memoryInformation->previous = NULL;
}
return;
}
// We can loop through the list without any problems, the head is not the pointer
while (tmp)
{
if (tmp->ptr == ptrToFree) // If we found the pointer that must be freed
{
toFree = tmp;
tmp = tmp->next;
previous = toFree->previous;
if (previous)
{
previous->next = tmp;
}
if (tmp)
{
tmp->previous = previous;
}
free(toFree->ptr);
if (toFree == memoryInformation)
{
memoryInformation = NULL;
}
free(toFree);
return;
}
tmp = tmp->next;
}
}
/**
* @brief printLeaks function is used to print all the memory leaks.
* @details This function is called when the program exits. It loop through the mem_info list and if it's not empty,
* it prints the memory leaks.
* @returns Nothing.
*/
void printLeaks()
{
mem_info* tmp = memoryInformation;
mem_info* previous = NULL;
size_t sum = 0;
int nbBlocks = 0;
if (tmp)
{
printf("Memory Leaks detected.\n");
}
while (tmp)
{
previous = tmp;
printf("\n%ld bytes lost\n", tmp->bytes);
printf("address : 0x%p in %s\t%s:%d\n", tmp->ptr, tmp->functionName, tmp->fileName, tmp->line);
printf("\n====================================\n");
sum += tmp->bytes;
tmp = tmp->next;
free(previous);
nbBlocks++;
}
printf("SUMMARY :\n%ld bytes lost in %d blocks\n", sum, nbBlocks);
}