Hello! Welcome back to our course on pointers.
In our last two lessons, we explored how to request memory from the heap using malloc() and calloc(). These functions are essential, but they share a limitation: they allocate a block of a fixed size. In many real-world applications, especially in systems that process variable amounts of data like communication stacks or data loggers, your memory requirements can change over time.
This brings us to a critical question: What do you do when an allocated block of memory is too small for new data, or perhaps wastefully large? Today's lesson answers that by introducing realloc(). You will learn how to use this powerful function to resize a previously allocated memory block safely, a skill that is crucial for building flexible and memory-efficient dynamic data structures.
The Need for Resizable Memory
Imagine you are implementing a gateway that logs CAN messages. You might start by allocating space for 100 messages, but what happens when the 101st message arrives? A fixed-size array will fail. The naive solution—allocating a huge buffer upfront—is wasteful and often impractical in memory-constrained embedded systems.
The realloc() function provides the elegant solution: it allows you to change the size of an existing memory allocation.
The function is declared in <stdlib.h> as:void* realloc(void* ptr, size_t new_size);
ptr: A pointer to a memory block previously allocated bymalloc(),calloc(), orrealloc().new_size: The new size for the memory block, in bytes.
It returns a pointer to the resized memory block. Critically, if realloc() succeeds, the contents of the block are preserved up to the minimum of the old and new sizes.
How realloc() Works: The Two Scenarios
When you call realloc(), one of two things can happen if the request is successful.
- In-place Expansion: If there is sufficient free memory immediately following your existing block,
realloc()may simply expand the block into that space. In this case, the memory address of your block does not change. - Relocation: If there is not enough contiguous space to expand the block,
realloc()will:
a. Find a new, larger memory block elsewhere on the heap.
b. Copy the contents from the old block to the new one.
c. Free the old memory block.
d. Return the address of the new block.
This image provides a clear visual summary of these two behaviors.

The fact that realloc() might move your memory block is the source of its power and its primary pitfall.
The Danger of Naive realloc() Usage
Because realloc() might return a new address, you must always update your pointer with its return value. A common but dangerous pattern looks like this:
// DANGEROUS - DO NOT DO THIS
ptr = realloc(ptr, new_size);
This single line of code hides a critical flaw. realloc() returns NULL if it fails to allocate the requested memory.
Consider what happens if realloc fails in the code above:
realloc()returnsNULL.- This
NULLvalue is assigned toptr, overwriting the original address of your memory block. - The original memory block is now lost. You have no pointer to it, so you can neither access its data nor
free()it.
This is a classic memory leak. In long-running systems like automotive ECUs, even small, repeated leaks can eventually exhaust system memory and lead to catastrophic failure.
The Safe realloc() Pattern
To use realloc() safely, you must use a temporary pointer to store its return value. This allows you to handle the failure case without losing your original memory block.
Here is the robust, correct pattern:
#include <stdlib.h>
// Assume 'ptr' points to a valid, dynamically allocated block
// and we want to resize it to 'new_size'.
// 1. Store the result in a temporary pointer
void* temp = realloc(ptr, new_size);
// 2. Check for allocation failure
if (temp == NULL) {
// Reallocation failed.
// 'ptr' is still valid and points to the original block.
// You can now decide how to handle the error.
// For example, log an error, free the original memory, and exit.
printf("Error: Memory reallocation failed.\n");
free(ptr); // Clean up the original block before exiting
// exit(1);
} else {
// 3. Success! Update the original pointer to the new address.
ptr = temp;
}
The following video provides an excellent walkthrough of these scenarios, culminating in the safe pattern using a temporary pointer.
This video from CodeVault clearly explains the three possible outcomes of calling realloc() and demonstrates why the naive approach is dangerous. Pay close attention to the final, correct implementation.
Watch from 02:00 to 08:59. The video details the three cases: in-place expansion, relocation, and failure. The most important section starts around 06:12, where it explains the memory leak pitfall and introduces the correct, safe pattern using a temporary pointer.
This safe-handling pattern is also clearly documented in the following article. It's a fundamental technique for robust C programming.
Dynamic Memory Allocation in C
For a quick textual summary of this crucial pattern, please read the following section from GeeksforGeeks.
Locate the realloc() section and read the subsection that starts with the sentence: 'It is important to note that if realloc() fails and returns NULL...'. It shows the correct C code for handling realloc failures using a temp pointer.
Test your understanding!
You are working on a data logger for an ADAS feature that records vehicle speed every second. You don't know how long the test drive will be, so you need a dynamic array. You start with space for 10 float values.
Below is a faulty implementation of a function add_speed_reading. It attempts to double the array capacity whenever it's full.
#include <stdlib.h>
#include <stdio.h>
// A struct to manage our dynamic array
typedef struct {
float* data;
size_t count; // current number of elements
size_t capacity; // allocated capacity
} SpeedLog;
// Faulty implementation
void add_speed_reading(SpeedLog* log, float speed) {
if (log->count == log->capacity) {
// Time to grow the array
size_t new_capacity = log->capacity * 2;
log->data = realloc(log->data, new_capacity * sizeof(float));
log->capacity = new_capacity;
}
// Add the new speed reading
log->data[log->count] = speed;
log->count++;
}
- Identify the critical bug in this function. What is the potential consequence?
- Rewrite the
add_speed_readingfunction to fix the bug and handle errors robustly. Ifreallocfails, the function should print an error and not modify theSpeedLogstruct.
Show answer
-
The Bug: The function uses the dangerous
log->data = realloc(...)pattern. Ifreallocfails (e.g., the system runs out of memory), it will returnNULL. ThisNULLwill be assigned tolog->data, causing a memory leak (the original data block is lost) and likely a crash on the next line (log->data[log->count] = speed;would be a NULL pointer dereference). -
Corrected Implementation:
#include <stdlib.h> #include <stdio.h> typedef struct { float* data; size_t count; size_t capacity; } SpeedLog; // Corrected, robust implementation void add_speed_reading(SpeedLog* log, float speed) { if (log->count == log->capacity) { // Time to grow the array size_t new_capacity = (log->capacity == 0) ? 10 : log->capacity * 2; // Handle initial case // Use a temporary pointer float* temp = realloc(log->data, new_capacity * sizeof(float)); // Check for failure if (temp == NULL) { printf("Error: Failed to expand speed log capacity.\n"); // The original log->data is still valid. We return without changing the log. return; } // Success! Update the struct's members. log->data = temp; log->capacity = new_capacity; } // Add the new speed reading log->data[log->count] = speed; log->count++; }This version correctly uses a
temppointer. Ifreallocfails, the original data is preserved, an error is printed, and the program can continue (or decide to stop logging). Thelogstruct remains in a valid state.
An Authoritative Answer: The C Standard
A common follow-up question is: "If realloc succeeds by moving my data, do I need to free() the old pointer?"
The answer is unequivocally no. The C standard itself provides the final word on this.
This Stack Overflow answer quotes the C standard directly, which is the ultimate source of truth for C's behavior. This clarifies exactly what realloc is guaranteed to do.
Find the answer by user John Bode and read the section that begins with 'Chapter and verse:'. This contains a quote from the C Standard (7.22.3.5 The realloc function). Pay attention to the parts in bold.
The two key takeaways from the standard are:
- "The
reallocfunction deallocates the old object pointed to byptr..." — This means on success,reallocdoes the cleanup for you. Freeing the old pointer yourself would cause a "double free" error. - "If memory for the new object cannot be allocated, the old object is not deallocated and its value is unchanged." — This confirms why the temporary pointer pattern is essential. On failure, you are still the owner of the original block and are responsible for freeing it eventually.
Conclusion
You've now learned how to dynamically resize memory allocations, a powerful technique for creating flexible data structures. More importantly, you've learned how to do it safely. For someone in your field, where software robustness is paramount, internalizing this safe pattern is non-negotiable.
Key Takeaways:
realloc(ptr, new_size)changes the size of a memory block, returning a pointer to the (potentially new) location.- A successful call might move the entire block, so you must update your pointer with the return value.
- Always use a temporary pointer to store the result of
realloc. This allows you to handle allocation failures gracefully without causing a memory leak. - On success,
reallocautomatically frees the old memory block. You must not free it yourself. - On failure, the original memory block remains valid and allocated. Your program is still responsible for managing it.
Preview of the Next Lesson:
The errors we've worked to avoid today—memory leaks and potential double-frees—are part of a family of common and dangerous pointer-related bugs. In our next lesson, we will systematically address these issues, learning to "Identify and prevent common memory errors such as double-free and use-after-free." This will equip you with the defensive mindset needed to write truly reliable C code.