Table of contents
Overview,
This article is intended for beginners and aims to provide a clear and concise overview of memory allocation in the C programming language using the malloc, calloc, and realloc functions. Memory allocation is an important aspect of programming, and knowing how to allocate and manage memory in C is critical for writing efficient and effective programmes.
Disclaimer
This article will not go in-depth on the memory allocation diagram as the functions discussed will mainly focus on dynamic memory allocation.
Memory allocation
Figure 1: Memory allocation diagram
In C memory allocation can be done statically, automatically or dynamically. The memory discussed is the random access memory (RAM).
Static memory refers to the allocation of memory at compile time. Allocation is done by the compiler that assigns a fixed amount of memory to a variable or data structure. It is important to note that the memory is fixed throughout the program's lifetime, meaning it does not increase or decrease during runtime. Naturally, static memory is stored in the data segment (also known as bss segment). All initialized static and global variables are contained within the data segment. While uninitialized static and global variables are stored in the bss segment. Some of the disadvantages of static memory are
If the user defines values in an array whose size is less than the size specified it leads to a waste of memory.
If the used enters values in an array whose size is greater than the specified size it leads to a crash in the program such as a segmentation fault.
A segmentation fault is a runtime error that occurs when a program tries to access a memory location it is not allowed 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. The memory is allocated when a function is called and deallocated when a function is returned. This memory is also known as stack memory as it is stored in the stack area.
Dynamic memory allocation is the process of allocating memory for a variable during program execution (runtime). The program determined the amount of memory needed during execution and does the allocating and deallocating as required. Dynamic memory is stored in the heap area on the memory allocation diagram where allocation and deallocation are done randomly. These built-in functions are used to carry out dynamic memory allocation
Malloc
Calloc
Realloc
Once memory has been allocated dynamically the memory is not automatically returned to the system for later use after execution. This causes a memory leak that compromises system performance. Therefore it is good practice to free dynamically allocated memory after execution using the free function.
It is important to note that allocated memory can only be accessed through pointers.
Using Malloc
Malloc is declared in the <stdlib.h> header file. It is used to dynamically allocate a single block of contiguous memory according to the specified size. Malloc does the allocation without knowledge of the type of data to be stored in the memory. This is because malloc does not initialize the allocated memory therefore the contents of the memory block are undefined. Always initialize the memory block before using it.
There are three golden rules that should be remembered when using the malloc() function
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 working with malloc typecasting can cause errors. Find more information here
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 mentioned earlier the free() function is used to release the 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 in the same way as malloc. The main difference between the two is calloc takes two arguments while malloc takes one argument. Moreover, calloc initializes its 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
Calloc returns a pointer to the beginning of the allocated memory block however if it fails it returns a null pointer.
Also, it's important to note that both calloc and malloc can fail to allocate memory if there is not enough 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: 0000000000include <stdlib.h>
Using Realloc
Realloc is the memory reallocate.
Syntax
ptr = (cast-type*)calloc(n, element-size);
Where
- ptr is reallocated with new size 'newSize'.
It deallocates an old object pointed to by the pointer and returns a pointer to a new object that has the size specified by size. The contents of the new object should be of the same size as that of the old object.
This function is used to change the size of the memory block without losing the old data. However, if the new size is smaller than the old size data may be lost. It is important to note that newly allocated bytes are uninitialized.
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
Memory allocation in programming is a lot like juggling balls. It takes a lot of concentration and expertise to keep everything in the appropriate spot. Fortunately, functions like malloc, calloc, and realloc can help you out in this balancing act. They each have their own set of advantages and disadvantages, but when used appropriately, they may assist you in dynamically allocating and managing memory like a pro. By mastering these approaches, you'll be able to create code that's capable of handling even the most demanding tasks.
So, keep these tips in mind and juggle away!