Introduction
Welcome to the next step in our journey through C's memory management capabilities. In the last two lessons, you learned how to allocate memory on the heap with malloc(), free it with free(), and, critically, how to use Valgrind to ensure you've cleaned up every byte you've borrowed. This gives you a solid foundation for handling memory you know the size of upfront.
But what happens when you don't? Our game's inventory is a perfect example. We want it to start empty and grow as the player picks up items. A fixed-size array is too rigid. This lesson introduces realloc(), the function that solves this exact problem by allowing you to resize a block of memory that's already on the heap. Mastering realloc() is the final piece of the puzzle you'll need to transform our static inventory into a truly dynamic one.
Why Do We Need to Resize Memory?
Before diving into realloc(), let's appreciate the problem it solves. Imagine you have a dynamically allocated array and it becomes full. How would you expand it using only the tools you know (malloc() and free())? The process would be manual and a bit clumsy.
Module 9: Dynamic Memory & ADTs in C
This document from the University of Waterloo CS department clearly outlines the manual process for resizing an array.
Please read the section titled Resizing arrays. This short section demonstrates the three-step dance required: allocate a new, larger block; copy the old data over; and free the original block.
This manual process works, but it's cumbersome and error-prone. You have to juggle multiple pointers (arr and old) and remember to free the old block to prevent a memory leak. The C standard library provides a single function, realloc(), to handle all of this for you.
The realloc() Function: Two Scenarios
The realloc() function attempts to change the size of a previously allocated memory block. Its signature is:
void *realloc(void *ptr, size_t new_size);
ptris the pointer to the memory block you want to resize.new_sizeis the desired new size in bytes.
When you call realloc(), one of two things can happen, depending on the memory layout on the heap.

- In-place Expansion: If there is enough free space immediately following your memory block,
realloc()can simply expand the block to the new size. In this case, the function returns the same pointer you passed to it. - Relocation: If there isn't enough contiguous space,
realloc()will find a new location on the heap that is large enough, allocate a block ofnew_size, copy the contents from the old block to the new one, and then automaticallyfree()the old block. In this scenario, it returns a new pointer to the new memory location.
Because the pointer value can change, you must always update your pointer with the return value of realloc(). However, as we'll see next, doing so naively is a classic C pitfall.
The Danger: realloc() Can Fail
What happens if realloc() cannot find a large enough block of memory anywhere on the heap? In this case, it fails and returns NULL. Crucially, if realloc() returns NULL, the original memory block is not freed. It remains valid and untouched.
This leads to a significant problem with the most common but incorrect usage pattern.
How To Safely Handle realloc() Failure | C Programming Example
The YouTube channel Portfolio Courses provides an excellent explanation of why the naive realloc pattern is so dangerous.
Please watch the segment from handling failure. The presenter clearly demonstrates how assigning the result of a failing realloc() call directly back to your original pointer leads to losing the only reference to your data, causing both a memory leak and a program crash.
As the video explains, if you write my_ptr = realloc(my_ptr, new_size); and realloc() fails:
realloc()returnsNULL.my_ptris overwritten withNULL.- You have now lost the address of your original data, making it impossible to access or free. You've created a memory leak.
- Any subsequent attempt to use
my_ptrwill likely be aNULLpointer dereference, crashing your program.
The Solution: The Safe realloc() Pattern
To use realloc() safely and robustly, you must use a temporary pointer to store its return value. This allows you to check for failure before overwriting your original pointer.
How To Safely Handle realloc() Failure | C Programming Example
The same video immediately follows up with the correct, safe approach.
Now, watch the solution presented from the safe pattern. This demonstrates the use of a temporary pointer to safely handle both successful and failed realloc calls.
The safe pattern looks like this:
// Assume 'inventory' is our dynamically allocated array of items
Item *temp = realloc(inventory, new_capacity * sizeof(Item));
if (temp == NULL) {
// realloc failed!
// The original 'inventory' pointer is still valid.
// We can try to clean up gracefully and exit.
fprintf(stderr, "Error: Could not expand inventory.\n");
free(inventory); // Free the original block
exit(EXIT_FAILURE);
} else {
// realloc succeeded!
// We can now safely update our main pointer.
inventory = temp;
}
This pattern is non-negotiable for professional C code. It ensures that you never lose your data, even if a memory allocation fails.
Application: Resizing an Array of Structs
Let's see this in the context of our game. We will eventually have a dynamic array of structs (e.g., Item or Player). The process is the same: you calculate the new total size and pass it to realloc().
Dynamically Allocate An Array Of Structs | C Programming Tutorial
This video demonstrates resizing an array of structs, which is directly applicable to our inventory management goal.
Watch the segment from reallocating structs. It shows how to expand an array of Point structs from 3 to 4 elements. As you watch, notice that the video uses the unsafe array = realloc(...) pattern. Based on what you've just learned, think about how you would rewrite that code using a temporary pointer to make it robust.
The key calculation is new_length * sizeof(struct_type), which gives realloc() the total number of bytes required for the resized array.
A Note on Performance: The Doubling Strategy
If you're building an inventory and add items one by one, you might be tempted to call realloc() every single time you add an item. This is extremely inefficient. Since realloc() may have to copy the entire array (an O(n) operation), doing this in a loop results in quadratic O(n²) performance.
A much better approach is the doubling strategy. When you run out of space, you don't just allocate space for one more item; you allocate space for double the current capacity.
Module 9: Dynamic Memory & ADTs in C
This document also provides a fantastic explanation of why resizing one-by-one is slow and how the doubling strategy provides excellent performance.
Please read the sections titled Amortized Cost and the doubling strategy explanation. Focus on the core idea: occasional expensive operations (realloc) are paid for by many cheap operations (simply adding an item to existing capacity), making the "average" or amortized cost very low.
This strategy is fundamental to how dynamic array structures (like C++'s std::vector or Python's list) are implemented under the hood. For our game inventory, it means that adding an item will almost always be instantaneous, with a brief, infrequent pause when the array needs to be resized.
Conclusion
You have now completed your essential C toolkit for dynamic memory. You can allocate memory (malloc), deallocate it (free), and now, crucially, resize it (realloc). You also understand the critical importance of handling realloc's potential failure to prevent memory leaks and crashes.
Key Takeaways:
realloc()is the standard library function for resizing a block of memory on the heap.- It may return the same pointer (for in-place expansion) or a new pointer (if the block was relocated).
- If
realloc()fails, it returnsNULL, and the original memory block remains valid. - Always use a temporary pointer to check the return value of
realloc()before assigning it back to your primary pointer. This is the safe and robust pattern. - When managing a growing collection like an inventory, the doubling strategy provides efficient, amortized constant-time performance for adding new elements.
In our next lesson, we will put all of this knowledge into practice. You will refactor the fixed-size, array-based inventory we built in Module 6 into a fully dynamic system using malloc and the safe realloc pattern, making a significant upgrade to our game project's foundation.