-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
250: Practice Exercise: Building a Simple Memory Leak Detector
I've noticed a recurring theme when I review code from developers moving into C: the belief that if a program doesn't crash and the OS cleans up everything after the process exits, then a "small" memory leak isn't actually a problem. You might think, "Why bother tracking a few bytes here and there if the kernel just wipes the slate clean when I hit Ctrl+C?"
The Myth: "If it doesn't crash, it's fine"
Here is why that thinking will get you fired in a production environment. Let's look at a specific scenario. Imagine you're writing a network packet processor. Every time a packet arrives, you allocate a small buffer to hold the header. If you forget to free that buffer, you might only be leaking 64 bytes per packet. That seems trivial, right?
while (server_running) {
char *header = malloc(64);
process_packet(header);
// Oops, forgot free(header);
}
On your laptop, during a five-minute test, you'll never notice. But in a production environment processing 10,000 packets per second, you're leaking roughly 640KB every second. In an hour, you've eaten 2.3GB of RAM. Eventually, the system hits the OOM (Out of Memory) killer, and your process is nuked. The tragedy is that the crash happens hours after the actual bug occurred, making it a nightmare to debug.
The Reality: Tracking the Lifecycle of Every Byte
Since C doesn't have a garbage collector, we have to be our own accountants. To build a leak detector, you can't just look at the pointers you currently have; you have to keep a record of every allocation you've ever made that hasn't been matched with a corresponding free.
The most effective way to do this without rewriting your entire codebase is to "wrap" the standard library functions. I usually create a custom my_malloc and my_free. Instead of just calling the original function, my wrapper adds the address and the size of the allocation to a global linked list. When my_free is called, I find that address in my list and remove it.
typedef struct Allocation {
void *address;
size_t size;
struct Allocation *next;
} Allocation;
Allocation *head = NULL;
void *my_malloc(size_t size) {
void *ptr = malloc(size);
if (ptr) {
Allocation *node = malloc(sizeof(Allocation));
node->address = ptr;
node->size = size;
node->next = head;
head = node;
}
return ptr;
}
Now, I have a ledger. If I call a report_leaks() function at the very end of main() and the head pointer isn't NULL, I know exactly how many bytes I leaked and where they are. (Note: In a real-world tool, you'd use a hash map for performance, but a linked list is the best way to understand the concept.)
Accounting for the Registry Itself
There's a catch here that often trips people up: my my_malloc function calls malloc to create the Allocation node itself. If I'm not careful, my leak detector will report its own bookkeeping nodes as leaks!
I handle this by using a separate flag or by calling the raw malloc for the internal nodes instead of the wrapped version. It's a bit of a "meta" problem, but it's the kind of detail that separates a tool that works from a tool that just creates noise.
📋 Practical Task
Implementation: The Allocation Ledger System
Your goal is to complete a simple memory leak detector. I've provided the skeleton of the wrapper functions, but the logic for tracking and reporting is missing. You need to implement the registry logic so the program can tell us exactly how much memory was forgotten.
Requirements:
- Implement
my_malloc: It should allocate the requested memory and then create anAllocationnode to store the address and size in a global linked list. - Implement
my_free: It should find the correspondingAllocationnode for the pointer being freed, remove that node from the list, and then call the realfree()on both the node and the original pointer. - Implement
report_leaks: This function should traverse the linked list. If the list is empty, print "No leaks detected!". If not, print the total number of bytes leaked and the number of allocations that weren't freed.
#include <stdio.h>
#include <stdlib.h>
typedef struct Allocation {
void *address;
size_t size;
struct Allocation *next;
} Allocation;
Allocation *registry = NULL;
void *my_malloc(size_t size) {
// TODO: Implement allocation and registry tracking
}
void my_free(void *ptr) {
// TODO: Implement registry removal and freeing
}
void report_leaks() {
// TODO: Implement the leak reporting logic
}
int main() {
void *a = my_malloc(100);
void *b = my_malloc(200);
my_free(a);
// We intentionally don't free 'b' to test the detector
report_leaks();
return 0;
}There are no comments for now.