Overview,
This article aims to provide a clear and concise overview of memory allocation in the C programming language using the malloc, calloc, and realloc functions. Memory management is a critical aspect of programming, and knowing how to allocate and manage memory in C is essential for writing efficient and effective programmes.
Disclaimer
The memory allocation diagram will not be discussed in detail because the functions discussed will primarily focus on dynamic memory allocation.
Memory allocation
In C memory allocation can be done statically, automatically or dynamically. The memory discussed is the random access memory (RAM).
Static memory is memory allocated at compile time. The compiler allocates memory by assigning a fixed amount of memory to a variable or data structure. It is important to note that memory remains constant throughout the program's lifespan, meaning it does not increase or decrease during runtime. Static memory is stored in the data segment (also known as bss segment). The data segment contains all initialised static and global variables. The bss segment stores uninitialized static and global variables. Some of the drawbacks of static memory are as follows:
If the user defines values in an array whose size is less than the size specified, memory is wasted.
If the used enters values in an array with a size greater than the specified size, the program crashes, resulting in a segmentation fault.
A segmentation fault is a runtime error that occurs when a program attempts to access a memory location that it is not permitted to access.
Code sample for statically allocated memory
{
int arr[4] = { 2, 4, 6, 8};
/*Here the allocated memory 4 is fixed */
}
Automatic memory allocation is the automatic process by the compiler to allocate and deallocate memory for local variables and function parameters. When a function is called, memory is allocated, and memory is deallocated when the function returns. Because it is stored in the stack area, this memory is also known as stack memory.
Dynamic memory allocation is the process of allocating memory for a variable during program execution (runtime). During execution, the program determines the amount of memory required and allocates and deallocates it as needed. Dynamic memory is stored in the heap area of the memory allocation diagram, where allocation and deallocation are done at random.
These built-in functions are used to perform dynamic memory allocation.
- Malloc
- Calloc
- Realloc
When memory is dynamically allocated, it is not automatically returned to the system for later use after execution. This results in a memory leak, which degrades system performance. As a result, it is best practise to use the free function to free dynamically allocated memory after execution.
It should be noted that allocated memory can only be accessed via pointers.
Using Malloc
Malloc is defined in the header file . It is used to allocate a single block of contiguous memory dynamically based on the size specified. Malloc allocates memory without knowing what type of data will be stored in it. Because malloc does not initialise the allocated memory, the contents of the memory block are undefined. Before using a memory block, it should always be initialised.
When using the malloc() function, there are three golden rules to remember.
- Only free memory that is allocated by malloc.
- Always free memory allocated by malloc.
- Do not free a block of memory more than once.
Syntax
ptr = (cast-type*) malloc(n * sizeof(type));
where:
- prt is a pointer of the type cast-type
- n is the number of elements you want to store
- Type is the data type of the elements
- sizeof operator is used to determine the size of the data type
- cast-type is used to cast the allocated memory block to the appropriate data type.
When using malloc, typecasting can lead to errors. More information can be found here:https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc
Code sample to allocate memory for 5 integers
int main()
{
int *ptr;
ptr = (int*) malloc(5 * sizeof(int));
}
The same code without typecast
int main()
{
int *ptr;
ptr = malloc(5 * sizeof(int));
}
On successful allocation malloc will return a pointer to the first byte of the allocated memory otherwise it will return NULL.
Instances when the malloc function is used;
- To allocate memory during program execution.
- To create dynamic-sized arrays or buffers whose size is unknown until runtime.
- To create dynamic data structures which require memory allocation at runtime. Malloc allocated memory for each node or element in the data structure.
As previously stated, the free() function is used to release dynamically allocated memory.
Syntax
free(var_name);
Code sample
#include <stdlib.h>
#include <stdio.h>
/**
* Write a program that dynamically allocates an array of integers
* of size n and initializes each element to its index value
*/
int main()
{
int *arr;
int i, n;
n = 8; /*set the value of n*/
arr = malloc(n * sizeof(arr + 1));
for (i = 0; i < n; i++)
{
arr[i] = i; /*initialize each element to its index value*/
printf("%d", arr[i]);
}
free(arr); //free the dynamically allocated memory
return (0);
}
The output is
01234567
Code sample two
#include <stdlib.h>
#include <stdio.h>
/**
* Write a program that dynamically allocates a 2D array (matrix) of
* size n x m and initializes each element to its product of
* row and column index values.
*/
int main() {
int n, m, i, j, **arr;
/*get column and row number from user*/
printf("Enter the number of rows: ");
scanf("%d", &n);
printf("Enter the number of columns: ");
scanf("%d", &m);
/*allocate memory for the 2D array*/
arr = (int**) malloc(n * sizeof(int*));
for (i = 0; i < n; i++) {
arr[i] = (int*) malloc(m * sizeof(int));
}
/*initialize each element of the array*/
for (i = 0; i < n; i++) {
for (j = 0; j < m; j++) {
arr[i][j] = i * j;
}
}
printf("The array is: \n");
for (i = 0; i < n; i++) {
for (j = 0; j < m; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
/*deallocate the dynamically allocated memory*/
for (i = 0; i < n; i++) {
free(arr[i]);
}
free(arr);
return 0;
}
The output
Enter the number of rows: 5
Enter the number of columns: 5
The array is:
0 0 0 0 0
0 1 2 3 4
0 2 4 6 8
0 3 6 9 12
0 4 8 12 16
Using Calloc
Calloc is a memory allocator that works similarly to malloc. The main distinction between the two is that calloc takes two arguments while malloc only takes one. Furthermore, calloc sets the allocated memory to zero.
Syntax
ptr = (cast-type*)calloc(n, element-size);
Where
- n is the number of elements to allocate
- element size is the size of each element in bytes
If Calloc succeeds, it returns a pointer to the start of the allocated memory block; otherwise, it returns a null pointer.
Also, both calloc and malloc can fail to allocate memory if there is insufficient memory available.
Code sample
#include <stdlib.h>
#include <stdio.h>
/**
* write a program thet allocates memory to an
* array is integers and initializes them using calloc
*/
int main()
{
int *arr, i;
int n = 10;
/*allocate the memory*/
arr = (int*) calloc(n, sizeof(int));
/*check if memory has been allocated*/
if (arr == NULL)
{
printf("Memory allocation failed \n");
exit(1);
}
printf("The array is: ");
for (i = 0; i < n; i++)
{
printf("%d", arr[i]);
}
printf("\n");
free(arr);
return (0);
}
The output
The array is: 0000000000
Using Realloc
The memory reallocate function is called realloc.
It returns a pointer to a new object with the size specified by size after deallocating an old object pointed to by the pointer. The new object's contents should be the same size as the old object.
This function is used to resize the memory block without erasing the previous data. However, data may be lost if the new size is smaller than the old size. It's worth noting that newly allocated bytes are uninitialized.
Syntax
ptr = (cast-type*)calloc(n, element-size);
Code sample
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr;
int n, i;
int new_n;
printf("Enter the initial size of the array: ");
scanf("%d", &n);
arr = (int *) malloc(n * sizeof(int));
printf("Enter %d integers:\n", n);
for (i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
printf("The array is:");
for (i = 0; i < n; i++) {
printf(" %d", arr[i]);
}
printf("\n");
printf("Enter the new size of the array: ");
scanf("%d", &new_n);
arr = (int *) realloc(arr, new_n * sizeof(int));
printf("Enter %d integers:\n", new_n);
for (i = n; i < new_n; i++) {
scanf("%d", &arr[i]);
}
printf("The resized array is:");
for (i = 0; i < new_n; i++) {
printf(" %d", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
The output
Enter the initial size of the array: 5
Enter 5 integers:
2 5 7 9 3
The array is: 2 5 7 9 3
Enter the new size of the array: 6
Enter 6 integers:
5
The resized array is: 2 5 7 9 3 5
To recap, memory allocation is critical in programming, and functions like malloc, calloc, and realloc can greatly assist with this. Each function has its own set of advantages and disadvantages, but they can all be used to dynamically allocate and manage memory. You can make your code more efficient, bug-free, and capable of handling larger workloads by understanding how they work and when to use them.
So remember these pointers the next time you're working on a programming project.
Top comments (1)
Great explanation. I understand memory allocation now.