C Dynamic Memory Allocation
In contrast to static memory allocation, which fixes data structure sizes at build time, dynamic memory allocation in C permits memory management during program execution. For instance, static array sizes are fixed. If the required size is unknown or fluctuates during runtime, resource waste or overflows may occur. Dynamic memory allocation allows realtime memory allocation. While running, it lets data structures like arrays change size dynamically. This strategy works well for data structures without a memory size limit, when the exact amount of memory needed is unclear, and when optimising memory space. C allocates memory dynamically using pointers and library functions.
The C programming language has many dynamic memory management methods. These essential routines for dynamic memory management are often found in the header file.
Dynamic memory Allocation Functions in C
The main C functions for dynamic memory allocation are as follows:
malloc()
- One block of memory of a given size can be dynamically allocated using the malloc() function. At execution time, it makes a memory request.
- The total number of bytes of storage to be allocated is the only input required.
- A void * pointer to the memory that was allotted is returned by the function. Any variable of any pointer type can be allocated a void * pointer, which is a general pointer type.
- Malloc() yields a NULL pointer if the memory cannot be allocated (for example, if there is not enough memory).
- It’s important to note that malloc() does not initialise the RAM that is allocated. There will be junk values in the memory block at first.
- The ANSI C prototype is void *malloc(size_t size) or unsigned long int size.
- Malloc() is often used with sizeof to determine the size of a data type or structure that needs space. NewPtr = malloc(sizeof(struct node)); allocates memory for a struct node object and stores a reference to it.
Example
#include <stdio.h>
#include <stdlib.h> // Required for malloc, free, and exit
int main() {
int* ptr; // Declare a pointer to an integer
int n, i;
// Define the number of elements for the array
n = 5;
printf("Enter number of elements: %d\n", n);
// Dynamically allocate memory using malloc()
// Allocate space for n integers. sizeof(int) gives the size of one integer in bytes.
// malloc expects the total number of bytes.
ptr = (int*)malloc(n * sizeof(int)); // [15, 34, 49] - Allocates n * size_of_int bytes
// Check if the memory has been successfully allocated
if (ptr == NULL) {
printf("Memory not allocated.\n");
exit(0); // Exit if allocation fails
} else {
// Memory has been successfully allocated
printf("Memory successfully allocated using malloc.\n");
// Use the allocated memory (e.g., assign values)
// The allocated memory can be treated like an array
for (i = 0; i < n; ++i) {
ptr[i] = i + 1; //
}
// Print the elements of the array
printf("The elements of the array are: ");
for (i = 0; i < n; ++i) {
printf("%d", ptr[i]);
if (i < n - 1) {
printf(", ");
}
}
printf("\n");
}
// It's crucial to free allocated memory when done
free(ptr); // Deallocate the memory pointed to by ptr
printf("Malloc Memory successfully freed.\n");
ptr = NULL; // Set pointer to NULL after freeing is good practice
return 0;
}
Output
Enter number of elements: 5
Memory successfully allocated using malloc.
The elements of the array are: 1, 2, 3, 4, 5
Malloc Memory successfully freed.
calloc()
- Memory for an array can be dynamically allocated using the calloc() method. It allots a predetermined number of elements, each of a predetermined size, contiguous memory space.
- It requires two inputs: the size of each element (size) and the number of elements in the array (nmemb or nitems). Type size_t, which is usually an unsigned integral type, is the type of both parameters.
- Calloc() returns a NULL pointer in the event that allocation fails, or a pointer to the allocated memory, just as malloc().
- Calloc() initialises all of the allocated memory’s bits to zero, which is a significant distinction from malloc(). The RAM it allots is cleared.
- void *calloc(size_t nmemb, size_t size)21… (or void *calloc(size_t nitems, size_t size))25. is the prototype.
Example
#include <stdio.h>
#include <stdlib.h> // Required for calloc, free, and exit
int main() {
int* ptr; // Declare a pointer to an integer
int n, i;
// Define the number of elements for the array
n = 5;
printf("Enter number of elements: %d\n", n);
// Dynamically allocate memory using calloc()
// Allocates space for n integers. sizeof(int) gives the size of one integer in bytes.
// calloc expects the number of elements and the size of each element.
// The memory is initialized to zeros by calloc.
ptr = (int*)calloc(n, sizeof(int)); // (int*) cast is optional but common
// Check if the memory has been successfully allocated
if (ptr == NULL) {
printf("Memory not allocated.\n");
exit(EXIT_FAILURE); // Exit if allocation fails
} else {
// Memory has been successfully allocated
printf("Memory successfully allocated using calloc.\n");
// Use the allocated memory (e.g., print values)
// Note: The memory is already initialized to zero by calloc
printf("Initial elements of the array (initialized by calloc): ");
for (i = 0; i < n; ++i) {
printf("%d", ptr[i]); // Access using array-like indexing
if (i < n - 1) {
printf(", ");
}
}
printf("\n");
// You can now assign new values if needed
for (i = 0; i < n; ++i) {
ptr[i] = (i + 1) * 10; // Assign new values
}
// Print the elements after assignment
printf("Elements after assignment: ");
for (i = 0; i < n; ++i) {
printf("%d", ptr[i]);
if (i < n - 1) {
printf(", ");
}
}
printf("\n");
}
// Free allocated memory when done
free(ptr); // Deallocate the memory pointed to by ptr
printf("Calloc Memory successfully freed.\n");
ptr = NULL; // Set pointer to NULL after freeing is good practice
return 0;
}
Output
Enter number of elements: 5
Memory successfully allocated using calloc.
Initial elements of the array (initialized by calloc): 0, 0, 0, 0, 0
Elements after assignment: 10, 20, 30, 40, 50
Calloc Memory successfully freed.
realloc()
- A memory block that has been allocated using malloc(), calloc(), or realloc() itself can have its size altered using the realloc() method. It can be applied to reduce or increase the amount of storage that is allotted.
- It requires two parameters: the new intended size in bytes and a pointer to the memory block that was previously allocated. void *realloc(void *ptr, size_t new_size); is the prototype.
- As long as the quantity of memory allocated is greater than the amount previously allocated, the contents of the original object remain unchanged. There is no guarantee that the contents of the newly added block section will be initialised if the size is raised.
Example
#include <stdio.h>
#include <stdlib.h> // Required for malloc, realloc, free, exit
#include <string.h> // For memset (optional, but useful for seeing new area)
int main() {
int *ptr = NULL; // Pointer to the dynamically allocated memory
int initial_size = 5;
int larger_size = 10;
int smaller_size = 3;
int i;
printf("--- Demonstrating realloc() ---\n");
// 1. Initial allocation using realloc(NULL, size) - works like malloc
// This is mentioned as a behavior of realloc.
printf("\nAttempting initial allocation for %d integers using realloc(NULL, ...)\n", initial_size);
ptr = (int *)realloc(NULL, initial_size * sizeof(int)); // (int*) cast is common
// Check if the initial allocation was successful
if (ptr == NULL) {
fprintf(stderr, "Initial realloc(NULL, ...) failed\n"); // Use stderr for errors
return EXIT_FAILURE; // Defined in stdlib.h
}
printf("Initial memory successfully allocated for %d integers.\n", initial_size);
// Fill the initially allocated memory (realloc(NULL,...) does not initialize)
printf("Filling initial %d elements...\n", initial_size);
for (i = 0; i < initial_size; ++i) {
ptr[i] = (i + 1) * 10;
}
// Print the initial elements
printf("Initial elements (%d): ", initial_size);
for (i = 0; i < initial_size; ++i) {
printf("%d", ptr[i]);
if (i < initial_size - 1) printf(", ");
}
printf("\n");
// 2. Reallocate to a larger size
// We must use a temporary pointer for the realloc result.
// If realloc fails, it returns NULL but the original ptr is still valid
printf("\nAttempting to reallocate to a larger size (%d integers)...\n", larger_size);
int *new_ptr = (int *)realloc(ptr, larger_size * sizeof(int));
// Check if the reallocation was successful
if (new_ptr == NULL) {
fprintf(stderr, "Reallocation to larger size failed. Original memory block is still valid.\n");
// In a real program, you might free(ptr) here or handle the error differently
free(ptr); // Free the original memory before exiting
ptr = NULL; // Good practice after freeing
return EXIT_FAILURE;
}
// Reallocation successful, update the main pointer
// The old 'ptr' is now invalid as realloc succeeded [10] and may have moved the block.
ptr = new_ptr;
printf("Memory successfully reallocated to %d integers.\n", larger_size);
// Fill the newly added memory area (not initialized by realloc)
// We'll fill elements from initial_size up to larger_size - 1
printf("Filling new elements (%d to %d)...\n", initial_size, larger_size - 1);
for (i = initial_size; i < larger_size; ++i) {
ptr[i] = (i + 1) * 100; // Assign different values
}
// Optional: Use memset to explicitly zero out the new area if needed, but not required by realloc
// memset(ptr + initial_size, 0, (larger_size - initial_size) * sizeof(int));
// Print elements after reallocation and filling new space
printf("Elements after increasing size (%d): ", larger_size);
for (i = 0; i < larger_size; ++i) {
printf("%d", ptr[i]);
if (i < larger_size - 1) printf(", ");
}
printf("\n");
// Note: The first 'initial_size' elements retain their original values.
// 3. Reallocate to a smaller size
printf("\nAttempting to reallocate to a smaller size (%d integers)...\n", smaller_size);
new_ptr = (int *)realloc(ptr, smaller_size * sizeof(int));
// Check if the reallocation was successful
if (new_ptr == NULL && smaller_size > 0) { // realloc(ptr, 0) might return NULL but succeed in freeing
fprintf(stderr, "Reallocation to smaller size failed. Original memory block is still valid.\n");
free(ptr); // Free the original memory before exiting
ptr = NULL;
return EXIT_FAILURE;
}
// Reallocation successful (or size was 0), update the main pointer
ptr = new_ptr;
printf("Memory successfully reallocated to %d integers.\n", smaller_size);
// Print elements after decreasing size
printf("Elements after decreasing size (%d): ", smaller_size);
for (i = 0; i < smaller_size; ++i) {
printf("%d", ptr[i]);
if (i < smaller_size - 1) printf(", ");
}
printf("\n");
// Note: Elements beyond the new size are lost and the memory is deallocated.
// 4. Free the final allocated memory
printf("\nFreeing the final allocated memory...\n");
free(ptr); // Deallocate the memory pointed to by ptr
printf("Memory successfully freed.\n");
ptr = NULL; // Set pointer to NULL after freeing is good practice
return EXIT_SUCCESS; // Defined in stdlib.h
}
Output
--- Demonstrating realloc() ---
Attempting initial allocation for 5 integers using realloc(NULL, ...)
Initial memory successfully allocated for 5 integers.
Filling initial 5 elements...
Initial elements (5): 10, 20, 30, 40, 50
Attempting to reallocate to a larger size (10 integers)...
Memory successfully reallocated to 10 integers.
Filling new elements (5 to 9)...
Elements after increasing size (10): 10, 20, 30, 40, 50, 600, 700, 800, 900, 1000
Attempting to reallocate to a smaller size (3 integers)...
Memory successfully reallocated to 3 integers.
Elements after decreasing size (3): 10, 20, 30
Freeing the final allocated memory...
Memory successfully freed.
free()
- Memory that has already been allocated by malloc(), calloc(), or realloc() can be released using the free() method. When memory used by these functions is no longer required, it is not automatically released.
- Memory can now be allocated in the future after being deallocated.
- The syntax is free(ptr);, where ptr is a pointer to the block of memory that has to be released. The prototype is free of voids (void * pointer).
- The system may experience a memory leak if dynamically allocated memory is not released when it is no longer required.
Example
#include <stdio.h> // For printf, fprintf
#include <stdlib.h> // Required for malloc, free, exit, EXIT_FAILURE, EXIT_SUCCESS
int main() {
int *intPtr = NULL; // Declare a pointer and initialize it to NULL
int num_elements = 5;
printf("--- Demonstrating free() ---\n");
// 1. Dynamically allocate memory using malloc()
// Request space for 'num_elements' integers.
// sizeof(int) determines the size of an integer in bytes.
printf("\nAttempting to allocate memory for %d integers using malloc()...\n", num_elements);
// malloc returns a void* pointer to the allocated memory.
// We cast it to int* and assign it to intPtr.
intPtr = (int *)malloc(num_elements * sizeof(int));
// 2. Always check if the memory allocation was successful
// malloc returns NULL if it cannot allocate the requested memory.
if (intPtr == NULL) {
fprintf(stderr, "Memory allocation failed! Exiting program.\n"); // Use stderr for errors
return EXIT_FAILURE; // EXIT_FAILURE is a symbolic constant defined in stdlib.h
}
// %p is standard for printing pointers, casting to (void*) is good practice
printf("Memory successfully allocated at address: %p\n", (void *)intPtr);
// 3. Use the allocated memory (optional, but shows it's usable)
printf("Accessing and writing to the allocated memory...\n");
for (int i = 0; i < num_elements; ++i) {
intPtr[i] = (i + 1) * 10; // Accessing elements using array notation
printf("intPtr[%d] = %d\n", i, intPtr[i]);
}
// Memory allocated by malloc is not guaranteed to be initialized,
// so we explicitly wrote values.
// 4. Free the allocated memory when you are finished with it
// Pass the pointer to the beginning of the allocated block to free().
// This returns the memory to the system for potential reuse.
printf("\nFreeing the allocated memory...\n");
free(intPtr);
printf("Memory successfully freed.\n");
// 5. (Recommended Good Practice) Set the pointer to NULL after freeing
// The pointer intPtr still holds the old address after free().
// Setting it to NULL prevents it from being a dangling pointer.
// Freeing a NULL pointer is safe and has no effect.
intPtr = NULL;
printf("Pointer set to NULL after freeing.\n");
// IMPORTANT: Do NOT attempt to access memory using intPtr after calling free(intPtr)
// unless you have reallocated or reassigned intPtr to valid memory.
// Also, do NOT call free(intPtr) again before allocating new memory.
// Memory allocated dynamically with malloc/calloc/realloc must be explicitly
// freed with free(). It is NOT automatically freed on function exit.
// Not freeing allocated memory causes a memory leak.
return EXIT_SUCCESS; // EXIT_SUCCESS is a symbolic constant defined in stdlib.h
}
Output
--- Demonstrating free() ---
Attempting to allocate memory for 5 integers using malloc()...
Memory successfully allocated at address: 0x63035300c6b0
Accessing and writing to the allocated memory...
intPtr[0] = 10
intPtr[1] = 20
intPtr[2] = 30
intPtr[3] = 40
intPtr[4] = 50
Freeing the allocated memory...
Memory successfully freed.
Pointer set to NULL after freeing.
sizeof Operator
- The sizeof operator is commonly used with dynamic memory methods, particularly calloc() and malloc(). It gives back the number of bytes of the supplied item, which may be an expression, variable, array name, basic data type name, or derived data type name.
- Sizeof makes the code more machine-independent by guaranteeing that the right amount of memory is allocated irrespective of the particular computer architecture or compiler. Sizeof(int), for instance, gives the size of an integer on the system where the code is compiled.
- Unless its parameter is a variable-length array, sizeof is an operator evaluated at compile time rather than a function.
In conclusion, dynamic memory allocation in C offers more flexibility than static allocation. The standard library functions malloc(), calloc(), realloc(), and free() allow memory allocation, resizing, and deallocation while a program is running. To guarantee proper, portable memory sizing, the sizeof operator is frequently utilised in conjunction with these capabilities. To avoid memory leaks, free() must be used correctly.
Example
#include <stdio.h> // Required for printf, fprintf
#include <stdlib.h> // Required for malloc, free, exit, EXIT_FAILURE
#include <stddef.h> // Required for size_t and offsetof (though offsetof is not used below, size_t is the return type)
// Define a simple structure to demonstrate sizeof on structs
typedef struct { // struct in C
int id;
double value;
char flag;
} my_struct; // typedef allows using my_struct without 'struct' keyword
int main() {
int basic_int;
double basic_double;
char basic_char;
int fixed_array; // Fixed-size array
int *int_pointer = NULL; // Pointer to int
my_struct s; // Variable of the struct type
printf("--- Demonstrating sizeof Operator ---\n");
// 1. sizeof on Basic Data Types
// Returns the number of bytes required to store a value of the specified type .
// Size can vary between systems. sizeof(char) is always 1.
// Parentheses are required when operand is a type name.
// The result is of type size_t .
printf("Size of basic types:\n");
// Using %%zu or %%lu for size_t is standard practice. %%zu is preferred in C99+.
printf(" sizeof(int) = %zu bytes\n", sizeof(int));
printf(" sizeof(double) = %zu bytes\n", sizeof(double));
printf(" sizeof(char) = %zu bytes\n", sizeof(char));
printf(" sizeof(float) = %zu bytes\n", sizeof(float));
printf(" sizeof(long) = %zu bytes\n", sizeof(long));
printf(" sizeof(long long) = %zu bytes\n", sizeof(long long));
printf(" sizeof(long double) = %zu bytes\n", sizeof(long double));
printf("\n");
// 2. sizeof on Variable Names
// Returns the number of bytes used to hold the variable.
// Parentheses are optional when operand is a variable name.
printf("Size of variables:\n");
printf(" sizeof(basic_int) = %zu bytes\n", sizeof basic_int); // Parentheses optional
printf(" sizeof(basic_double) = %zu bytes\n", sizeof basic_double);
printf(" sizeof(basic_char) = %zu bytes\n", sizeof basic_char);
printf("\n");
// 3. sizeof on an Expression
// Returns the number of bytes required to hold the result of the expression.
printf("Size of an expression:\n");
printf(" sizeof(basic_int + basic_double) = %zu bytes\n", sizeof(basic_int + basic_double));
printf(" sizeof('A') = %zu bytes\n", sizeof('A')); // Character literal
printf("\n");
// 4. sizeof on a Fixed-Size Array Name
// When applied to an array name, sizeof returns the total number of bytes in the entire array.
printf("Size of a fixed-size array:\n");
printf(" sizeof(fixed_array) = %zu bytes (total size of the array)\n", sizeof(fixed_array));
// To get the number of elements in a fixed-size array, divide the total size by the size of one element:
printf(" Number of elements in fixed_array = %zu\n", sizeof(fixed_array) / sizeof(fixed_array));
// Or divide by the size of the type: sizeof(fixed_array) / sizeof(int)
printf("\n");
// 5. sizeof on a Pointer
// When applied to a pointer, sizeof returns the size of the pointer variable itself,
// NOT the size of the data type it points to.
// This size is typically 4 or 8 bytes depending on the system's architecture (32-bit vs 64-bit).
printf("Size of a pointer:\n");
printf(" sizeof(int_pointer) = %zu bytes (size of the pointer variable)\n", sizeof(int_pointer));
printf("\n");
// 6. sizeof on a Structure
// Returns the total number of bytes occupied by the structure, including any padding added by the compiler for alignment.
// For unions, sizeof returns the size of the largest member.
printf("Size of a structure:\n");
printf(" sizeof(my_struct) = %zu bytes\n", sizeof(my_struct));
printf("\n");
// 7. Using sizeof with Dynamic Memory Allocation
// sizeof is commonly used with functions like malloc and calloc to allocate the correct amount
// of memory for a type or structure in a machine-independent way.
my_struct *struct_ptr = NULL; // Pointer to hold address of allocated memory
printf("Using sizeof with dynamic memory allocation:\n");
// Allocate memory for one my_struct
// malloc(size_t size) allocates 'size' bytes.
// We use sizeof(my_struct) to get the required size.
printf("Attempting to allocate memory for one my_struct...\n");
struct_ptr = (my_struct *)malloc(sizeof(my_struct)); // Cast recommended in C, optional for void* in C++
if (struct_ptr == NULL) {
fprintf(stderr, "Memory allocation failed!\n");
return EXIT_FAILURE;
}
printf("Memory allocated for my_struct at address: %p\n", (void *)struct_ptr);
// Use the allocated memory (optional)
struct_ptr->id = 1;
struct_ptr->value = 3.14;
struct_ptr->flag = 'Y';
printf("Populated allocated memory.\n");
// Remember to free dynamically allocated memory when done [Intro, see thought process].
printf("Freeing allocated memory...\n");
free(struct_ptr); // Deallocates the memory block [Intro, see thought process].
printf("Memory freed.\n");
// Good practice: Set pointer to NULL after freeing to avoid dangling pointer [Intro, see thought process].
struct_ptr = NULL;
printf("Pointer set to NULL.\n");
// sizeof is generally evaluated at compile time, but at runtime for Variable-Length Arrays (VLAs).
// (VLAs are a C99 feature, not standard C++)
printf("\nsizeof operator is a powerful tool for portable code.\n");
return 0;
}
Output
--- Demonstrating sizeof Operator ---
Size of basic types:
sizeof(int) = 4 bytes
sizeof(double) = 8 bytes
sizeof(char) = 1 bytes
sizeof(float) = 4 bytes
sizeof(long) = 8 bytes
sizeof(long long) = 8 bytes
sizeof(long double) = 16 bytes
Size of variables:
sizeof(basic_int) = 4 bytes
sizeof(basic_double) = 8 bytes
sizeof(basic_char) = 1 bytes
Size of an expression:
sizeof(basic_int + basic_double) = 8 bytes
sizeof('A') = 4 bytes
Size of a fixed-size array:
sizeof(fixed_array) = 76 bytes (total size of the array)
Number of elements in fixed_array = 1
Size of a pointer:
sizeof(int_pointer) = 8 bytes (size of the pointer variable)
Size of a structure:
sizeof(my_struct) = 24 bytes
Using sizeof with dynamic memory allocation:
Attempting to allocate memory for one my_struct...
Memory allocated for my_struct at address: 0x5e715f9b76b0
Populated allocated memory.
Freeing allocated memory...
Memory freed.
Pointer set to NULL.
sizeof operator is a powerful tool for portable code.
You can also read What Are The Unions In C And How To Declare It?