Absolutely. Let’s dissect [[asymmetric_pthreads/17_malloc_leak_detached_threads]]
— one of the sneakiest, most common, and most expensive bugs in multithreaded systems.
💥 [[asymmetric_pthreads/17_malloc_leak_detached_threads]]
🧠 “No Join, No Free, No Mercy.”
📂 Source: 17_malloc_leak_detached_threads.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define THREADS 5
void *worker(void *arg)
{
int index = *(int *)arg;
char *data = malloc(100);
if (!data)
{
perror("malloc failed");
pthread_exit(NULL);
}
snprintf(data, 100, "Hello from thread %d\n", index);
printf("%s", data);
usleep(100000); // simulate work
// OOPS: we never free(data)
return (NULL); // and main never join()s us
}
int main(void)
{
pthread_t t[THREADS];
int ids[THREADS];
for (int i = 0; i < THREADS; i++)
{
ids[i] = i;
if (pthread_create(&t[i], NULL, worker, &ids[i]) != 0)
{
perror("pthread_create failed");
exit(EXIT_FAILURE);
}
pthread_detach(t[i]); // ← we're detaching immediately
}
printf("Main is done launching detached threads.\n");
sleep(1);
printf("Main exits without joining or cleanup.\n");
return (0);
}
🧩 What This Code Looks Like It Does
✅ Creates 5 threads
✅ Each thread prints a string
✅ Detaches them (so main doesn’t need to join)
✅ Program exits
💣 What It Actually Does
🔥 Hidden Behavior | Consequence |
---|---|
malloc() in thread | Allocates memory ✅ |
free() is never called | 💥 memory leak |
Thread is detached | No way to recover memory after pthread_exit() |
No pthread_join() | Main can’t synchronize or clean up |
Program exits after 1s | May exit before some threads finish |
🧠 Mental Model Upgrade: Detach = Fire-and-Forget
When you call pthread_detach()
, you’re telling the OS:
“Hey, I’m never gonna join this thread.
Just clean up the memory and return value automatically when it’s done.”
But:
🛑 That only cleans up the thread struct, not the heap allocations the thread makes.
So if your thread malloc()
s something — and you never free()
it — it leaks, even if the thread itself terminates correctly.
🧪 Valgrind Output (Simplified)
==12345== HEAP SUMMARY:
==12345== definitely lost: 500 bytes in 5 blocks
==12345== indirectly lost: 0 bytes in 0 blocks
==12345== still reachable: 0 bytes in 0 blocks
==12345== suppressed: 0 bytes in 0 blocks
📉 That’s 5 threads × 100 bytes = 500 leaked bytes
Now imagine doing this in a real server 1000s of times per day. 💸
✅ How to Fix
Option 1: Free the memory inside the thread
free(data);
return (NULL);
Option 2: Avoid detach()
, and collect the result via pthread_join()
This is what real daemons do — they don’t leak, they track:
pthread_join(t[i], NULL);
🧠 Truth Bomb
title: “Detached” Means Nobody Is Watching
- No join = no ability to wait
- No wait = no chance to capture returned values
- No return handling = no cleanup
🧠 Why This Is Asymmetric
Because you’d never know it’s leaking unless:
You ran
valgrind
Or the program ran long enough to blow up memory
Or you analyzed code flow and saw malloc without free
Most developers don’t spot this until it costs millions of bytes in production.
✅ Checkpoint
🔍 Goal | ✅ |
---|---|
Uses pthread_create() | ✅ |
Uses pthread_detach() | ✅ |
Calls malloc() in threads | ✅ |
Omits free() | ✅ |
Causes heap memory leaks | ✅ |
No crash, no warning | ✅ |
Valgrind required to detect | ✅ |
Teachable fix exists | ✅ |
📦 Obsidian Vault Suggestions
🚀 Want to Level It Up?
Say the word and I’ll deliver:
-
🔥 Version that leaks structs, file descriptors, and pipes
-
🧠 Annotated visual timeline of how leaked memory accumulates
-
🧪 Valgrind command presets + suppression rules
-
📦 Test file
18_return_struct_from_detached.c
with broken access
Shall we push into [[asymmetric_pthreads/18_struct_return_from_thread]]
next?