Dynamic memory allocation allows programs to allocate and manage memory at runtime according to their requirements. It is useful when the amount of memory needed by a program is not known at compile time.
- Dynamic memory is allocated from the heap.
- The allocated memory can be resized using realloc().
- Dynamically allocated memory should be released using free() when it is no longer needed.
Dynamic Memory Allocation Functions
C provides four main functions for dynamic memory management: malloc(), calloc(), realloc(), and free(). These functions are declared in the <stdlib.h> header file.
malloc()
The malloc() function allocates a contiguous block of memory on the heap at runtime. The allocated memory is uninitialized, so its contents have an indeterminate value.
Syntax
ptr = malloc(size);
The size specifies the number of bytes to allocate, and malloc() returns a pointer to the allocated memory. If the allocation fails, it returns NULL.
Example: Suppose we want to dynamically allocate memory for 5 integers.
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = malloc(5 * sizeof(int));
if (ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}
for (int i = 0; i < 5; i++) {
ptr[i] = i + 1;
}
for (int i = 0; i < 5; i++) {
printf("%d ", ptr[i]);
}
free(ptr);
return 0;
}
Output
1 2 3 4 5
Explanation: malloc(5 * sizeof(int)) allocates enough memory to store 5 integers, while ptr stores the address of the allocated memory. The allocated memory is used like an array, and free(ptr) releases it after use.
Checking malloc() Failure
Memory allocation may fail if the requested amount of memory is unavailable. Therefore, the returned pointer should be checked against NULL before using the allocated memory.
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = malloc(5 * sizeof(int));
if (ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}
printf("Memory allocated successfully\n");
free(ptr);
return 0;
}
Output
Memory allocated successfully
Note: In C, an explicit cast such as (int *)malloc(...) is not required. malloc() returns a void *, which can be implicitly converted to another object pointer type in C.

This function returns a void pointer to the allocated memory that needs to be converted to the pointer of required type to be usable. If allocation fails, it returns NULL pointer.
calloc()
The calloc() function allocates memory for multiple elements and initializes all allocated bytes to zero.
Syntax
ptr = calloc(number_of_elements, size_of_each_element);
Unlike malloc(), calloc() accepts the number of elements and the size of each element separately.
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = calloc(5, sizeof(int));
if (ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}
for (int i = 0; i < 5; i++) {
printf("%d ", ptr[i]);
}
free(ptr);
return 0;
}
Output
0 0 0 0 0
Explanation: calloc(5, sizeof(int)) allocates memory for 5 integers and initializes the allocated bytes to zero. The pointer ptr can then be used to access the allocated memory like an array.

This function also returns a void pointer to the allocated memory that is converted to the pointer of required type to be usable. If allocation fails, it returns NULL pointer.
free()
The free() function releases dynamically allocated memory that is no longer required. Memory allocated using malloc(), calloc(), or realloc() should eventually be released using free().
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = calloc(5, sizeof(int));
if (ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}
for (int i = 0; i < 5; i++) {
printf("%d ", ptr[i]);
}
free(ptr);
ptr = NULL;
return 0;
}
Output
0 0 0 0 0
Explanation: free(ptr) releases the memory allocated for ptr, making that memory available for future use. Setting ptr to NULL after free() helps prevent accidental use of the invalid pointer.

After freeing a memory block, the pointer becomes invalid, and it is no longer pointing to a valid memory location.
realloc()
The realloc() function changes the size of a previously allocated memory block. It can be used to increase or decrease the amount of dynamically allocated memory.
Syntax
ptr = realloc(ptr, new_size);
If the memory block is successfully resized, realloc() returns a pointer to the resized block. If it fails, it returns NULL and the original memory block remains allocated.
Example: Suppose we initially allocate memory for 5 integers and later need space for 10 integers.
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = malloc(5 * sizeof(int));
if (ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}
for (int i = 0; i < 5; i++) {
ptr[i] = i + 1;
}
int *temp = realloc(ptr, 10 * sizeof(int));
if (temp == NULL) {
printf("Memory reallocation failed\n");
free(ptr);
return 1;
}
ptr = temp;
for (int i = 5; i < 10; i++) {
ptr[i] = i + 1;
}
for (int i = 0; i < 10; i++) {
printf("%d ", ptr[i]);
}
free(ptr);
return 0;
}
Output
1 2 3 4 5 6 7 8 9 10
Explanation: The program initially allocates memory for 5 integers and then uses realloc() to resize the block to hold 10 integers. A temporary pointer is used to safely handle reallocation failure without losing the original memory block.

It is important to note that if realloc() fails and returns NULL, the original memory block is not freed, so you should not overwrite the original pointer until you've successfully allocated a new block. To prevent memory leaks, itβs a good practice to handle the NULL return value carefully:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *ptr = (int *)malloc(5 * sizeof(int));
// Reallocation
int *temp = (int *)realloc(ptr, 10 * sizeof(int));
// Only update the pointer if reallocation is successful
if (temp == NULL)
printf("Memory Reallocation Failed\n");
else
ptr = temp;
return 0;
}
Practical Example
Consider the first scenario where we were having issues with the fixes size array. Let's see how we can resolve both of these issues using dynamic memory allocation.
#include <stdio.h>
#include <stdlib.h>
int main() {
// Initially allocate memory for 5 integers
int *ptr = (int *)malloc(5 * sizeof(int));
// Check if allocation was successful
if (ptr == NULL) {
printf("Memory Allocation Failed\n");
exit(0);
}
// Now, we need to store 8 elements so
// Reallocate to store 8 integers
ptr = (int *)realloc(ptr, 8 * sizeof(int));
// Check if reallocation was successful
if (ptr == NULL) {
printf("Memory Reallocation Failed\n");
exit(0);
}
// Assume we only use 5 elements now
for (int i = 0; i < 5; i++) {
ptr[i] = (i + 1) * 10;
}
// Shrink the array back to 5 elements
ptr = (int *)realloc(ptr, 5 * sizeof(int));
// Check if shrinking was successful
if (ptr == NULL) {
printf("Memory Reallocation Failed\n");
exit(0);
}
for (int i = 0; i < 5; i++)
printf("%d ", ptr[i]);
// Finally, free the memory when done
free(ptr);
return 0;
}
Output
10 20 30 40 50
Explanation: In this program, we are managing the memory allocated to the pointer ptr according to our needs by changing the size using realloc(). It can be a fun exercise to implement an array which grows according to the elements inserted in it. This kind of arrays are called dynamically growing arrays.
Common Issues in Dynamic Memory Allocation
Dynamic memory allocation provides flexibility, but incorrect memory management can lead to errors and undefined behavior.
- Memory Leaks: Failing to free dynamically allocated memory leads to memory leaks, exhausting system resources.
- Dangling Pointers: Using a pointer after freeing its memory can cause undefined behavior or crashes.
- Fragmentation: Repeated allocations and deallocations can fragment memory, causing inefficient use of heap space.
- Allocation Failures: If memory allocation fails, the program may crash unless the error is handled properly.