MemTrack is a lightweight runtime memory debugging and leak detection library written in C.
It tracks dynamic memory allocations, detects common memory-management errors, records allocation source information, and provides runtime memory statistics.
- Tracks
malloc,calloc,realloc, andfree - Memory leak detection
- Double-free detection
- Invalid free and invalid realloc detection
realloc()failure handling- Freed-address reuse handling
calloc()overflow protection- Allocation IDs and metadata
- Source file, line, and function tracking
- Allocation type tracking
- Detailed free diagnostics
- Active and peak memory statistics
- Active and peak allocation counts
- Custom hash table with separate chaining
- Automated stress, collision, and edge-case tests
User Program
|
v
MemTrack API
+------+------+------+------+
| | | |
malloc calloc realloc free
| | | |
+------+------+------+------+
|
v
Tracking Layer
/ \
v v
Allocation Hash Freed Address
Table Table
\ /
\ /
v v
Diagnostics
|
+---------+---------+
| |
Leak Report Memory Statistics
MemTrack uses the standard C allocation functions internally rather than implementing its own memory allocator.
Each active allocation is stored in a custom hash table along with its metadata.
Every tracked allocation contains:
Address
Size
Allocation ID
Source file
Source line
Source function
Allocation type
The public allocation APIs use macros to capture the caller's location:
#define mt_malloc(size) \
mt_malloc_debug(size, __FILE__, __LINE__, __func__)This allows leak reports to identify where an allocation originated.
Example:
[LEAK]
ID: 12
Type: malloc
Address: 0x55...
Size: 100 bytes
Allocated at: main.c:24
Function: main
MemTrack uses a hash table with separate chaining for active allocations.
The allocation address is converted to uintptr_t and hashed into a fixed number of buckets.
Bucket 0 -> AllocationNode -> AllocationNode
Bucket 1 -> NULL
Bucket 2 -> AllocationNode
...
Bucket 15 -> AllocationNode -> AllocationNode
Separate chaining allows multiple addresses to occupy the same bucket.
The address is shifted before hashing to reduce the effect of alignment-related low bits on typical 64-bit systems.
For a well-distributed table, allocation lookup, insertion, and removal are O(1) on average.
At shutdown, MemTrack scans the allocation table for allocations that are still active.
The report includes:
- Allocation ID
- Type
- Address
- Size
- Source location
Freed addresses are maintained separately so MemTrack can distinguish a double free from an invalid free.
Example:
[ERROR] Double Free
Address: 0x55...
Originally allocated at: tests/test_memtrack.c:120
Allocation function: main
Previously freed at: tests/test_memtrack.c:125
Free function: main
If an address exists in neither the active allocation table nor the freed-address table, MemTrack reports an invalid free.
Before calling the system realloc(), MemTrack verifies that the address belongs to an active tracked allocation.
When a new allocation receives an address that previously belonged to a freed allocation, the old freed-address record is removed.
This prevents a valid new allocation from being incorrectly reported as a double free.
realloc() requires special handling because the memory address may change.
Existing allocation
|
v
Remove from hash table
|
v
realloc()
/ \
fail success
| |
v v
Restore Update address
node and size
If realloc() fails, the original allocation remains valid and its tracking information is restored.
If it succeeds, the existing allocation record is updated and inserted into the appropriate hash bucket.
The allocation ID and original allocation metadata are preserved.
MemTrack tracks:
Allocation Operations
malloc calls
calloc calls
realloc calls
free calls
Memory Usage
Active allocations
Peak allocations
Active bytes
Peak bytes
Statistics can be displayed using:
mt_print_stats();Example:
===== MemTrack Statistics =====
Allocation Operations
malloc calls: 120
calloc calls: 25
realloc calls: 43
free calls: 150
Memory Usage
Active allocations: 38
Peak allocations: 72
Active bytes: 3420
Peak bytes: 12840
MemTrack/
├── include/
│ └── memtrack.h
├── src/
│ ├── main.c
│ └── memtrack.c
├── tests/
│ └── test_memtrack.c
├── build/
├── .gitignore
├── LICENSE
├── Makefile
└── README.md
main.c contains a demonstration program, while test_memtrack.c contains the automated test suite.
Build the project:
makeRun the demonstration:
./memtrackRun the test suite:
make testClean generated build files:
make cleanThe test suite covers:
mallocandcalloctrackingreallocgrowth and shrinkingrealloc()failurerealloc(ptr, 0)- Double frees
- Invalid frees
- Invalid reallocations
free(NULL)- Address reuse
malloc(0)calloc(0, size)calloc()overflow- Same-size reallocations
- Hash-table collisions
- Hash-bucket movement
- Allocation metadata
- Memory statistics
- Stress testing with large numbers of allocations
- Leak detection
- Shutdown and reset behavior
MemTrack wraps the standard C allocation functions instead of implementing a custom allocator. This keeps the project focused on runtime tracking, diagnostics, and memory analysis.
Separate chaining was chosen for hash collisions because multiple allocation addresses can map to the same bucket.
__FILE__, __LINE__, and __func__ are captured at the caller's location through macros. This makes allocation and free diagnostics more useful.
The allocation record is temporarily removed before calling realloc(). If the operation fails, the original record is restored because the original memory is still valid.
MemTrack currently requires programs to use the MemTrack APIs:
mt_malloc()
mt_calloc()
mt_realloc()
mt_free()It does not automatically intercept direct calls to the standard allocation functions.
Possible future extensions include:
LD_PRELOAD-based automatic interception- Stack trace collection
- Thread-safe tracking
- Configurable logging
- Allocation filtering
- Runtime visualization
This project is licensed under the MIT License.