C
Completed
-
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
55: Resizing with realloc and Avoiding Leaks
I was looking over some old code for a sensor-logging utility I wrote a few years back. The goal was simple: read a series of temperature readings into an array. The problem? I had no idea how many readings the sensor would actually send. I started with a small buffer, but as soon as the data exceeded that size, the program would either crash or start overwriting other parts of memory. Let's walk through how I fixed this using realloc, and more importantly, why the "obvious" way to use it is actually a trap.
Starting with a guess
Initially, I just grabbed a small chunk of memory. I figured 4 integers would be enough for a quick test. It looks like this:
int *readings = malloc(4 * sizeof(int));
// ... fill the array with 4 values ...
But then I ran a real test, and the sensor sent 10 readings. If I try to write to readings[4], I'm in undefined behavior territory. I need more space. C provides realloc for exactly this—it tries to expand the existing block of memory or, if it can't, it finds a new, larger spot, copies your data over, and frees the old block for you.
The "Quick and Dirty" mistake
When I first learned C, I did what most people do. I just assigned the result of realloc directly back to my original pointer. It feels efficient, right?
readings = realloc(readings, 10 * sizeof(int));
Here is the problem: realloc can fail. If the system is out of memory, it returns NULL. If that happens in the line above, I've just overwritten my only pointer to the original 4 integers with NULL. The original memory is still allocated, but I no longer have the address to it. I've just created a memory leak that I can't even fix with free() because I've lost the handle. I've seen this crash production servers because the leak happens inside a loop.
The safe hand-off
To fix this, I have to use a temporary pointer. I'll let realloc tell me if it succeeded before I commit to updating my main pointer. Let's try this approach instead:
int *temp = realloc(readings, 10 * sizeof(int));
if (temp == NULL) {
// Handle the error. The original 'readings' pointer is still valid!
fprintf(stderr, "Failed to expand buffer\n");
free(readings);
exit(1);
}
readings = temp;
Now, if realloc fails, readings still points to my original 4 integers. I can either try to recover, save the data I have, or shut down cleanly by freeing the original pointer. This "temporary bridge" is the only way to use realloc safely.
The final cleanup
One thing that often trips people up is thinking that because realloc might move the memory to a new location, they need to free the old pointer manually. You don't. If realloc moves the block, it handles the free of the old block automatically. You only ever need to call free on the final pointer you're holding once you're completely done with the data.
Let's see it all put together in a way that actually handles a stream of data:
int *buffer = malloc(2 * sizeof(int));
int capacity = 2;
int count = 0;
while (has_more_data()) {
if (count == capacity) {
int new_capacity = capacity * 2;
int *temp = realloc(buffer, new_capacity * sizeof(int));
if (!temp) {
perror("realloc failed");
free(buffer);
return 1;
}
buffer = temp;
capacity = new_capacity;
}
buffer[count++] = get_next_reading();
}
free(buffer); // One free to rule them all
I like doubling the capacity (the capacity * 2 part). If you resize by only 1 element every time, you're calling realloc constantly, which kills performance. Doubling it means you resize less often as the list grows.
📋 Practical Task
The Dynamic Integer Accumulator
Write a program that reads integers from the user one by one. The user will signal they are finished by entering the number 0.
- Start with an initial allocation for 2 integers.
- Every time the array fills up, use
reallocto double its size. - Ensure you use a temporary pointer during
reallocto prevent memory leaks in case of failure. - Once the user enters
0, print the total number of elements stored and the sum of all elements. - Properly free the memory before the program exits.
There are no comments for now.