mirror of https://github.com/TheAlgorithms/C
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>
This commit is contained in:
parent
e81bc16832
commit
3682694f76
|
@ -48,6 +48,7 @@ endif()
|
|||
|
||||
## Add subdirectories containing CMakeLists.txt
|
||||
# to configure and compile files in the respective folders
|
||||
add_subdirectory(developer_tools)
|
||||
add_subdirectory(hash)
|
||||
add_subdirectory(misc)
|
||||
add_subdirectory(games)
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
# If necessary, use the RELATIVE flag, otherwise each source file may be listed
|
||||
# with full pathname. RELATIVE may makes it easier to extract an executable name
|
||||
# automatically.
|
||||
file( GLOB APP_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c )
|
||||
# file( GLOB APP_SOURCES ${CMAKE_SOURCE_DIR}/*.c )
|
||||
# AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} APP_SOURCES)
|
||||
|
||||
add_library(malloc_dbg malloc_dbg.c) # Create a static library from malloc_dbg.c file
|
||||
|
||||
foreach( testsourcefile ${APP_SOURCES} )
|
||||
string( REPLACE ".c" "" testname ${testsourcefile} )
|
||||
string( REPLACE ".C" "" testname ${testname} )
|
||||
string( REPLACE " " "_" testname ${testname} )
|
||||
|
||||
if ("${testsourcefile}" STREQUAL "malloc_dbg.c")
|
||||
continue()
|
||||
endif()
|
||||
|
||||
add_executable( ${testname} ${testsourcefile} )
|
||||
|
||||
if ("${testname}" STREQUAL "test_malloc_dbg")
|
||||
target_link_libraries(${testname} malloc_dbg)
|
||||
endif()
|
||||
|
||||
if(OpenMP_C_FOUND)
|
||||
target_link_libraries(${testname} OpenMP::OpenMP_C)
|
||||
endif()
|
||||
|
||||
if(MATH_LIBRARY)
|
||||
target_link_libraries(${testname} ${MATH_LIBRARY})
|
||||
endif()
|
||||
# target_link_libraries(${testname} malloc_dbg) # Link the malloc_dbg library
|
||||
install(TARGETS ${testname} DESTINATION "bin/developer_tools")
|
||||
|
||||
endforeach( testsourcefile ${APP_SOURCES} )
|
|
@ -0,0 +1,289 @@
|
|||
/**
|
||||
* @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);
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* @file
|
||||
* @brief Header file that contains macros used to replace malloc/calloc and free.
|
||||
* @details
|
||||
* Macros malloc, calloc and free respectively calls malloc_dbg, calloc_dbg and free_dbg.
|
||||
* malloc_dbg and calloc_dbg allocates memory using the "real" malloc and calloc and store
|
||||
* the pointer returned (with additional informations) in a linked list.
|
||||
* Thanks to this linked list, it is possible to check memory leaks.
|
||||
* @author [tinouduart33](https://github.com/tinouduart33)
|
||||
* @see malloc_dbg.c
|
||||
*/
|
||||
|
||||
#ifndef MALLOC_DBG_H
|
||||
#define MALLOC_DBG_H
|
||||
|
||||
/** This macro replace the standard malloc function with malloc_dbg.
|
||||
* */
|
||||
#define malloc(bytes) malloc_dbg(bytes, __LINE__, __FILE__, __FUNCTION__)
|
||||
|
||||
/** This macro replace the standard calloc function with calloc_dbg.
|
||||
* */
|
||||
#define calloc(elemCount, elemSize) calloc_dbg(elemCount, elemSize, __LINE__, __FILE__, __FUNCTION__)
|
||||
|
||||
/** This macro replace the standard free function with free_dbg.
|
||||
* */
|
||||
#define free(ptr) free_dbg(ptr)
|
||||
|
||||
void* malloc_dbg(size_t bytes, int line, const char* filename, const char* functionName);
|
||||
|
||||
void* calloc_dbg(size_t elementCount, size_t elementSize, int line, const char* filename, const char* functionName);
|
||||
|
||||
void free_dbg(void* ptrToFree);
|
||||
|
||||
void printLeaks(void);
|
||||
|
||||
#endif /* MALLOC_DBG_H */
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* @file
|
||||
* @brief File used to test the malloc_dbg, calloc_dbg and free_dbg functions.
|
||||
* @details
|
||||
* This file only have a main function that calls malloc, calloc and free.
|
||||
* When the program exits, memory leaks must be printed.
|
||||
* @author [tinouduart33](https://github.com/tinouduart33)
|
||||
* @see malloc_dbg.c, malloc_dbg.h
|
||||
*/
|
||||
|
||||
#include <stdio.h> /// For IO operations if needed.
|
||||
#include <stdlib.h> /// For the EXIT_SUCCESS macro and the "real" malloc, calloc and free functions.
|
||||
#include "malloc_dbg.h" /// For the macros malloc, calloc and free and the malloc_dbg, calloc_dbg and free_dbg functions.
|
||||
|
||||
|
||||
/**
|
||||
* @brief Main function
|
||||
* @param argc number of arguments (not used)
|
||||
* @param argv list of arguments (not used)
|
||||
* @returns 0 on exit
|
||||
*/
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
int* iptr = malloc(10 * sizeof(int));
|
||||
char* cptr = calloc(256, sizeof(char));
|
||||
|
||||
free(iptr);
|
||||
// free(cptr);
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue