Dynamic Memory Allocation
Allocating memory dynamically using `malloc`, `calloc`, and `realloc`. Freeing allocated memory using `free` to avoid memory leaks.
Resizing Memory with realloc
in C
In C programming, the realloc
function is a powerful tool for dynamically resizing memory blocks that have been previously allocated using malloc
, calloc
, or realloc
itself. This allows you to adjust the amount of memory your program uses during runtime, which is particularly useful when dealing with data structures whose size is not known in advance.
Understanding realloc
The realloc
function has the following prototype (defined in stdlib.h
):
void *realloc(void *ptr, size_t size);
ptr
: A pointer to the memory block that you want to resize. This pointer must have been previously returned by a memory allocation function (malloc
,calloc
, orrealloc
). Ifptr
isNULL
,realloc
behaves the same asmalloc(size)
, allocating a new memory block of the specified size.size
: The new desired size of the memory block, in bytes.
Return Value:
- If successful,
realloc
returns a pointer to the (potentially moved) memory block. The contents of the memory block up to the minimum of the old and new sizes are guaranteed to be preserved. - If
size
is zero,realloc
returnsNULL
and frees the original memory block pointed to byptr
. It's equivalent tofree(ptr)
. - If
realloc
cannot allocate the requested memory (e.g., due to insufficient memory), it returnsNULL
. Crucially, in this case, the original memory block pointed to byptr
remains valid and untouched. This is why handling the potentialNULL
return is so important.
How realloc
Works
realloc
attempts to resize the existing memory block in place. If there is enough contiguous memory available after the current block, it will simply extend the block to the new size and return the original pointer. However, if there is not enough contiguous memory, realloc
will:
- Allocate a new memory block of the requested size.
- Copy the contents of the original memory block (up to the minimum of the old and new sizes) to the new block.
- Free the original memory block.
- Return a pointer to the new memory block.
Because realloc
might move the memory block, you should always update your pointer to the block with the value returned by realloc
.
Examples
Example 1: Initial Allocation and Resizing
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = NULL;
size_t size = 5;
// Initial allocation using malloc
arr = (int *)malloc(size * sizeof(int));
if (arr == NULL) {
perror("malloc failed");
return 1;
}
printf("Initial allocation: %zu elements\n", size);
// Fill the array
for (size_t i = 0; i < size; i++) {
arr[i] = (int)i;
}
// Resize the array to hold 10 elements
size = 10;
int *temp = (int *)realloc(arr, size * sizeof(int)); // Use a temporary pointer!
if (temp == NULL) {
perror("realloc failed");
free(arr); // Important: Free the original memory if realloc fails
return 1;
}
arr = temp; // Only assign the new pointer if realloc succeeded
printf("Resized allocation: %zu elements\n", size);
// Fill the new elements
for (size_t i = 5; i < size; i++) {
arr[i] = (int)i;
}
// Print the array
for (size_t i = 0; i < size; i++) {
printf("arr[%zu] = %d\n", i, arr[i]);
}
free(arr); // Free the allocated memory
return 0;
}
Example 2: Handling realloc
Failure
#include <stdio.h>
#include <stdlib.h>
int main() {
int *data = malloc(100 * sizeof(int));
if (data == NULL) {
perror("malloc failed");
return 1;
}
// Attempt to allocate a very large amount of memory (likely to fail)
int *temp = realloc(data, SIZE_MAX); //Using SIZE_MAX to trigger failure
if (temp == NULL) {
perror("realloc failed");
//IMPORTANT - Do not use 'data', it remains valid.
printf("realloc failed, original pointer is still valid\n");
free(data); // Free the original allocated memory.
return 1;
} else{
data = temp;
printf("Reallocation sucessful with pointer: %p", (void*)data);
}
free(data);
return 0;
}
The Importance of Using a Temporary Pointer
Always use a temporary pointer when calling realloc
. If realloc
fails and returns NULL
, the original memory block pointed to by ptr
remains valid. If you directly assign the return value of realloc
to ptr
without checking for NULL
and realloc
fails, you'll lose the original pointer and create a memory leak. Using a temporary pointer allows you to check the return value and, if realloc
fails, you can still access and free the original memory block.
This is the correct and safe pattern:
void *temp = realloc(ptr, new_size);
if (temp == NULL) {
// realloc failed. ptr is still valid.
// Handle the error (e.g., print an error message, free ptr, exit)
free(ptr);
ptr = NULL;
// ...
} else {
// realloc succeeded
ptr = temp;
}
Important Considerations
- Memory Leaks: If you repeatedly call
realloc
without properly freeing the old memory block (in the case of failure), you'll create a memory leak. - Error Handling: Always check the return value of
realloc
forNULL
to handle potential allocation failures. - Efficiency: While
realloc
is a powerful tool, frequent resizing can be inefficient, especially if it involves copying large amounts of data. Consider using a more suitable data structure if you anticipate frequent resizing. - Zero Size: If you call
realloc(ptr, 0)
, it is equivalent tofree(ptr)
. The return value will be NULL.