I've been following an article that walks through implementing primitive versions of malloc and free. After finishing the allocator, I wanted to test it using Valgrind, so I added the following lines to my malloc and free functions, respectively:
VALGRIND_MALLOCLIKE_BLOCK(block, size, 0, 0);
VALGRIND_FREELIKE_BLOCK(block, 0);
Valgrind shows that memory allocations are being tracked correctly using VALGRIND_MALLOCLIKE_BLOCK()
and VALGRIND_FREELIKE_BLOCK()
. However, one block is still marked as "still reachable" at exit.
HEAP SUMMARY:
==74425== in use at exit: 20 bytes in 1 blocks
==74425== total heap usage: 2 allocs, 2 frees, 1,044 bytes allocated
==74425==
==74425== 20 bytes in 1 blocks are still reachable in loss record 1 of 1
==74425==
==74425== LEAK SUMMARY:
==74425== definitely lost: 0 bytes in 0 blocks
==74425== indirectly lost: 0 bytes in 0 blocks
==74425== possibly lost: 0 bytes in 0 blocks
==74425== still reachable: 20 bytes in 1 blocks
==74425== suppressed: 0 bytes in 0 blocks
The article adds a "stub" member to the union, with a fixed size of 16, however it is unnecessary, since sizeof(header_t) = 24
.
Here's my implementation of the two functions:
pthread_mutex_t global_malloc_lock;
header_t *head, *tail;
header_t *get_free_block(size_t size) {
header_t *curr = head;
while (curr) {
if (curr->s.is_free && curr->s.size >= size) {
return curr;
}
curr = curr->s.next;
}
return NULL;
}
void *my_malloc(size_t size) {
size_t total_size;
void *block;
header_t *header;
if (!size) {
return NULL;
}
pthread_mutex_lock(&global_malloc_lock);
header = get_free_block(size);
if (header) {
header->s.is_free = 0;
pthread_mutex_unlock(&global_malloc_lock);
return (void*)(header+1);
}
total_size = sizeof(header_t) + size;
block = sbrk(total_size);
if (block == (void*) -1) {
pthread_mutex_unlock(&global_malloc_lock);
return NULL;
}
header = block;
header->s.is_free = 0;
header->s.size = size;
header->s.next = NULL;
if (!head) {
head = header;
}
if (tail) {
tail->s.next = header;
}
tail = header;
pthread_mutex_unlock(&global_malloc_lock);
VALGRIND_MALLOCLIKE_BLOCK(block, size, 0, 0);
return (void*)(header+1);
}
void my_free(void* block) {
header_t *header, *tmp;
void *programbreak;
if (!block) {
return;
}
pthread_mutex_lock(&global_malloc_lock);
header = (header_t*)block - 1;
programbreak = sbrk(0);
if ((char*)block + header->s.size == programbreak) {
if (head == tail) {
head = tail = NULL;
}
else {
tmp = head;
while(tmp) {
if (tmp->s.next == tail) {
tmp->s.next = NULL;
tail = tmp;
}
tmp = tmp->s.next;
}
}
sbrk(0 - sizeof(header_t) - header->s.size);
pthread_mutex_unlock(&global_malloc_lock);
VALGRIND_FREELIKE_BLOCK(block, 0);
return;
}
header->s.is_free = 1;
pthread_mutex_unlock(&global_malloc_lock);
}
(The definition of the union as well as the signatures for the functions are in a separate header file)
Here's the main function:
int main(int argc, char const *argv[])
{
printf("sizeof(header_t) = %zu\n", sizeof(header_t));
int* block = my_malloc(5*sizeof(int));
my_free(block);
return 0;
}
My question is: why does Valgrind report memory as "still reachable" even though I call my_free()
and VALGRIND_FREELIKE_BLOCK()
? Is it because sbrk()
isn't releasing the memory? Or is there something missing in my free logic?
The article: https://arjunsreedharan.org/post/148675821737/memory-allocators-101-write-a-simple-memory
As for the still reachable
memory issue, I was able to fix it by changing the Valgrind free notifier from:
VALGRIND_FREELIKE_BLOCK(block, 0);
to:
VALGRIND_FREELIKE_BLOCK(header, 0);
This change ensures that Valgrind is correctly notified of the entire allocated block (including the header), allowing it to recognize the memory as properly freed:
HEAP SUMMARY:
==129253== in use at exit: 0 bytes in 0 blocks
==129253== total heap usage: 2 allocs, 2 frees, 1,044 bytes allocated
==129253==
==129253== All heap blocks were freed -- no leaks are possible
However, I still need to address some suppressed errors reported by Valgrind, mainly invalid writes, and conditional jumps depending on uninitialized values, which leads me to think that my pointer arithmetic might be slightly flawed. Here's a snippet of the Valgrind output for reference:
1 errors in context 1 of 9:
==129253== Conditional jump or move depends on uninitialised value(s)
==129253== at 0x497E838: brk (brk.c:37)
==129253== by 0x497E8F3: __sbrk (sbrk.c:74)
==129253== by 0x497E8F3: sbrk (sbrk.c:36)
==129253== by 0x109875: my_free (in /home/projects/allocators/main)
==129253== by 0x10924E: main (in /home/projects/allocators/main)
==129253==
==129253==
==129253== 1 errors in context 2 of 9:
==129253== Syscall param brk(end_data_segment) contains uninitialised byte(s)
==129253== at 0x497E82B: brk (brk.c:36)
==129253== by 0x497E8F3: __sbrk (sbrk.c:74)
==129253== by 0x497E8F3: sbrk (sbrk.c:36)
==129253== by 0x109875: my_free (in /home/projects/allocators/main)
==129253== by 0x10924E: main (in /home/projects/allocators/main)
==129253==
==129253==
==129253== 1 errors in context 3 of 9:
==129253== Conditional jump or move depends on uninitialised value(s)
==129253== at 0x497E8E9: __sbrk (sbrk.c:66)
==129253== by 0x497E8E9: sbrk (sbrk.c:36)
==129253== by 0x109875: my_free (in /home/projects/allocators/main)
==129253== by 0x10924E: main (in /home/projects/allocators/main)
==129253==
==129253==
==129253== 1 errors in context 4 of 9:
==129253== Conditional jump or move depends on uninitialised value(s)
==129253== at 0x497E8B9: __sbrk (sbrk.c:66)
==129253== by 0x497E8B9: sbrk (sbrk.c:36)
==129253== by 0x109875: my_free (in /home/projects/allocators/main)
==129253== by 0x10924E: main (in /home/projects/allocators/main)
==129253==
==129253==
==129253== 1 errors in context 5 of 9:
==129253== Conditional jump or move depends on uninitialised value(s)
==129253== at 0x497E8B7: sbrk (sbrk.c:62)
==129253== by 0x109875: my_free (in /home/projects/allocators/main)
==129253== by 0x10924E: main (in /home/projects/allocators/main)
==129253==
==129253==
==129253== 1 errors in context 6 of 9:
==129253== Conditional jump or move depends on uninitialised value(s)
==129253== at 0x1097DE: my_free (in /home/projects/allocators/main)
==129253== by 0x10924E: main (in /home/projects/allocators/main)
==129253==
==129253==
==129253== 1 errors in context 7 of 9:
==129253== Invalid write of size 8
==129253== at 0x109699: my_malloc (in /home/projects/allocators/main)
==129253== by 0x10923E: main (in /home/projects/allocators/main)
==129253== Address 0x403c010 is in the brk data segment 0x403c000-0x403c02b
==129253==
==129253==
==129253== 1 errors in context 8 of 9:
==129253== Invalid write of size 8
==129253== at 0x10968F: my_malloc (in /home/projects/allocators/main)
==129253== by 0x10923E: main (in /home/projects/allocators/main)
==129253== Address 0x403c000 is in the brk data segment 0x403c000-0x403c02b
==129253==
==129253==
==129253== 1 errors in context 9 of 9:
==129253== Invalid write of size 4
==129253== at 0x10967A: my_malloc (in /home/projects/allocators/main)
==129253== by 0x10923E: main (in /home/projects/allocators/main)
==129253== Address 0x403c008 is in the brk data segment 0x403c000-0x403c02b