In C language, memory is allocated at runtime using the memory management functions (calloc, malloc … etc.). The memory management functions are guaranteed that if the memory is allocated, it would be suitably aligned to any object which has the fundamental alignment. The fundamental alignment is less than or equal to the largest alignment that’s supported by the implementation without an alignment specification.
The dynamic memory allocation resolves a lot of problems that are faced by the developer during the development. One of the biggest problems with dynamic memory allocation in C programming is that it is not destroyed by itself either compiler. It is only destroyed by the developer to call the free function explicitly, sometimes it becomes the root of the problems. You can see my other article, problems with dynamic memory allocation.
In this article, I will discuss 15 common mistakes with memory allocation that are generally done by the developers. If you are fresher and you have no basic idea of the dynamic memory allocation then it is my advice to read my article memory management functions before reading this article. So let’s see the common mistakes with memory allocation in C programming.
1.)Forget to check the return value of malloc:
It is a very common mistake and can be the cause of the segmentation fault. When we call the malloc (memory management function) then it returns the pointer to the allocated memory. If there is no free space is available, the malloc function returns the NULL. It is good habits to verify the allocated memory because it can be NULL. You already know that if we try to dereference the null pointer, we will get the segmentation fault.
Let’s see the example code,
In the below code, everything is fine until the malloc function doesn’t return the null pointer. If malloc returns the NULL, the code will crash.
#include<stdio.h> #include<stdlib.h> int main(void) { int *piBuffer = NULL; int n = 10, i = 0; //creating integer of size n. piBuffer = malloc(n * sizeof(int)); //Assigned value to allocated memory for (i = 0; i < n; ++i) { piBuffer [i] = i * 3; } //Print the value for (i = 0; i < n; ++i) { printf("%d\n", piBuffer[i]); } //free up allocated memory free(piBuffer); return 0; }
We can resolve the above problem to verify the return value of the malloc function. If malloc returns the null pointer, the code will display an error message and terminate the execution.
#include<stdio.h> #include<stdlib.h> int main(void) { int *piBuffer = NULL; int n = 10, i = 0; //creating integer of size n. piBuffer = malloc(n * sizeof(int)); //make sure piBuffer is valid or not if (piBuffer == NULL) { // allocation failed, exit from the program fprintf(stderr, "Out of memory!\n"); exit(1); } //Assigned value to allocated memory for (i = 0; i < n; ++i) { piBuffer[i] = i * 3; } //Print the value for (i = 0; i < n; ++i) { printf("%d\n", piBuffer[i]); } //free up allocated memory free(piBuffer); return 0; }
2.) Initialization errors:
Generally, c programmer uses malloc to allocate the block of memory. Some programmers assume that malloc allocated memory is initialized by the zero and they use the block of memory without any initialization. In some scenarios, it does not reflect the bad effect but sometimes it creates hidden issues.
Let’s see the example code,
In the below code, the programmer incorrectly assumes that the value of the allocated memory is zero and performs some arithmetical operation.
int * Foo(int *x, int n) { int *piBuffer = NULL; int i = 0; //creating an integer array of size n. piBuffer = malloc(n * sizeof(int)); //make sure piBuffer is valid or not if (piBuffer == NULL) { // allocation failed, exit from the program fprintf(stderr, "Out of memory!\n"); exit(1); } //Add the value of the arrays for (i = 0; i < n; ++i) { piBuffer[i] = piBuffer[i] + x[i]; } //Return allocated memory return piBuffer; }
Note: If you have required initialized memory, use the memset() along with malloc or called the calloc() that allocate the initialized memory.
3.) Access the already freed memory:
When you freed the allocated memory then still pointer pointing to the same address. if you attempt to read or write the freed pointer then might be you succeed but it is illegal and can be the cause of the code crashing. It is also a reason to born the dangling pointer.
#include<stdio.h> #include<stdlib.h> int main() { int *piData = NULL; piData = malloc(sizeof(int) * 10); //creating integer of size 10. free(piData); //free the allocated memory *piData = 10; //piData is dangling pointer return 0; }
4.) Freeing the same memory multiple times:
A free function is used to deallocate the allocated memory. If piData (arguments of free) is pointing to a memory that has been deallocated (using the free or realloc function), the behavior of free function would be undefined.
The freeing of the memory twice is more dangerous then memory leak, so it is very good habits to assigned the NULL to the deallocated pointer because the free function does not perform anything with the null pointer.
#include<stdio.h> #include<stdlib.h> int main() { int *piData = NULL; //creating integer of size 10. piData = malloc(sizeof(int) * 10); if(piData == NULL) { return -1; } //free the allocated memory free(piData); //free the allocated memory twice free(piData); return 0; }
5.) Freeing memory that was not allocated by memory management function:
The free function only deallocates the allocated memory. If piData is not pointing to a memory that is allocated by the memory management function, the behavior of the free function will be undefined.
Let’s see the example code,
In the below code, I am getting the segmentation fault because I am trying to deallocate the auto variable memory using the free function.
#include<stdio.h> #include<stdlib.h> int main() { int Data = 0; int *piData = &Data; //free the memory free(piData); return 0; }
6.) Forget to free the allocated memory:
The dynamically allocated memory only destroy by the programmer to calling the free function explicitly. If the programmer forgets to deallocate the allocated memory, then the allocated memory is not available to another process and it is reserved for the entire life of the program. It is one of the important causes of memory leaks. The memory leak is a common and dangerous problem and type of resource leak.
int main () { char * pBuffer = malloc(sizeof(char) * 20); /* Do some work */ /*Not freeing the allocated memory*/ return 0; }
Note: once you allocate a memory than allocated memory does not allocate to another program or process until it gets free.
7.) Using malloc () in the wrong place:
Declaration of the normal array is easy and fast. The allocated memory of the normal array is automatically released by the compiler when the control comes out from the function. On the other hand, dynamic memory allocation is slow and only released by the developer explicitly to call the free function. So it is beneficial to use the normal array when the array is not needed after the function returns.
Note: This technique is supported by the C99 or C11 compilers.
Let’s see the example code,
In the below code, I am describing the place where is the normal array is better than the dynamic array.
void Foo(int n) { int *piBuffer = NULL; //creating an integer array of size n. piBuffer = malloc(n * sizeof(int)); //make sure piBuffer is valid or not if (piBuffer == NULL) { // allocation failed, exit from the program fprintf(stderr, "Out of memory!\n"); exit(1); } free(piBuffer); }
Now we can do the above work using below simple static array in C.
void Foo(int n) { int piBuffer[n]; //normal array }
You can also see the article, how to create the 1D and 2D array in c.
8.) Calculating the size of the dynamic array using the sizeof operator:
Some developers use the sizeof operator to calculate the size of the dynamically allocated array. The sizeof operator is used to calculate the size of the static array, not used for the dynamic array. If you tried to calculate the size of the dynamic array then you will get the size of the pointer.
#include<stdio.h> #include<stdlib.h> int main (void) { int *piBuffer = NULL; int n = 10; //creating an integer array of size n. piBuffer = malloc(n * sizeof(int)); //make sure piBuffer is valid or not if (piBuffer == NULL) { // allocation failed, exit from the program fprintf(stderr, "Out of memory!\n"); exit(1); } printf("%d\n",sizeof(piBuffer)); free(piBuffer); return 0; }
Output: 8 bytes (64-bit machine)
So It is a great idea to carry the length of the dynamic array. Whenever you have required the length of the array, you need to read the stored length. To implement this idea in the program we need to allocate some extra space to store the length. It is my advice, whenever you use the technique then check that length of the array should not exceed the type of the array.
For example,
Suppose you need to create an integer array whose size is n. So to carry the array length of the array, you need to allocate the memory for n+1
int *piArray = malloc ( sizeof(int) * (n+1) );
If memory is allocated successfully, assign n (size of the array) its 0 places.
piArray[0] = n; or * piArray = n;
Now it’s time to create a copy of the original pointer but to left one location from the beginning.
int * pTmpArray = piArray +1;
Note: if you are new, see this article arithmetic operation on the pointer.
Now, whenever in a program you ever required the size of the array then you can get from copy pointer.
ArraySize = pTmpArray[-1];
After using the allocated memory don’t forget to deallocate the allocated memory.
free (piArray);
9.) Improper use of the memory management function:
It is very important to use the memory management function in proper ways. Some developer uses the zero size malloc in their program. It is very dangerous because if the size of the requested space is zero, the behavior will be implementation-defined. The return value of the malloc could be a null pointer or it shows the behavior like that size is some nonzero value.
In the below program, I am using the zero size malloc. The output of the zero size malloc is implementation-defined, so it will be dangerous to use the returned value of the malloc.
#include<stdio.h> #include<stdlib.h> int main (void) { int *piBuffer = NULL; //creating an integer array of size n. piBuffer = malloc(0 * sizeof(int)); //make sure piBuffer is valid or not if (piBuffer == NULL) { // allocation failed, exit from the program fprintf(stderr, "Out of memory!\n"); exit(1); } printf("%d\n",sizeof(piBuffer)); free(piBuffer); return 0; }
Output: Implementation-dependent
10.) Does not counting the number of allocated memory:
It is good habits to count the number of allocated memory in the program, this method prevents the memory leak and freed the memory multiple times.
In this technique, we will create two global counters and initialize them with 0. In every successful allocation, we will increment the value of the counter1 (Allocate_Counter ) and after the deallocating the memory we will increment the counter2 (Deallocate_Counter). In the end of the application, the value of both counters should be equal.
This method helps you to track the status of allocated memory. To implement this technique we need to create three customize functions, one for memory allocation and second for memory deallocation and last one to check the memory leak.
static unsigned int Allocate_Counter = 0; static unsigned int Deallocate_Counter = 0; void *Memory_Allocate (size_t size) { void *pvHandle = NULL; pvHandle = malloc(size); if (NULL != pvHandle) { ++Allocate_Counter; } else { //Log error } return (pvHandle); } void Memory_Deallocate (void *pvHandle) { if(pvHandle != NULL) { free(pvHandle); ++Deallocate_Counter; } } int Check_Memory_Leak(void) { int iRet = 0; if (Allocate_Counter != Deallocate_Counter) { //Log error iRet = Memory_Leak_Exception; } else { iRet = OK; } return iRet; }
11.) Accessing a dynamic array out of boundaries:
It is a common mistake that is done by the developers. When you access the dynamic array out of the boundary then the behavior of your program can be undefined. We can resolve this problem to put a check condition before accessing the array.
Let see an example code,
#include <stdio.h> #include <stdlib.h> int main() { int *piData = NULL; int n = 10; //size of the array int pos = 0; //allocate memory piData = malloc(sizeof(int) * n); if(piData == NULL) { return -1; } for(pos = 0; pos < n; pos++) { piData[pos] = 10; } printf("Enter the array index = "); // Enter the index which you want to read scanf("%d",&pos); //put the boundary check condition if( pos < n) { printf("%d\n",piData[pos]); } //deallocate memory free(piData); return 0; }
Output1:
Enter the array index = 3
10
Output2:
Enter the array index = 12
Enter the valid index
12.) Working on the original pointer:
It can be a reason for the undefined behavior, it creates a problem when there is accidental change occurred on the pointers. To prevents undefined behavior it is a good habit to work on a copy of the pointer, it preserves the address of allocating memory. If there is any accidental change occurred on the pointer, this technique helps you to get the actual address of allocating memory that is needed at the time of memory deallocation.
int *pBuffer = malloc ( sizeof(char) * 10 ); //Create copy of the pointer int *pTmpBuffer = pBuffer; // Do some work free (pBuffer);
13.) Re-assignment of pointer:
Sometimes reassignment of the pointer creates the problems. If you do not use the dynamically allocated memory properly (in the situation of shallow copy), it can cause the code crashing or unwanted result.
#include <stdio.h> #include <stdlib.h> int main() { int *piData1 = NULL; int *piData2 = NULL; //allocate memory piData1 = malloc(sizeof(int)); if(piData1 == NULL) { return -1; } *piData1 = 100; printf(" *piData1 = %d\n",*piData1); piData2 = piData1; printf(" *piData1 = %d\n",*piData2); //deallocate memory free(piData1); *piData2 = 50; printf(" *piData2 = %d\n",*piData2); return 0; }
In the above example, piData1 and piData2 are two pointers. I am allocating the memory to piData1 using the malloc and assigned 100 to the allocated memory.
If I will assign the allocated memory to the pointer piData2, the allocated memory is shared by both pointers.
When you will free the memory that is pointed by the piData1 than you will get an undefined result for accessing piData2.
14.) Dereferencing a pointer without allocating some memory:
When you will try to access a pointer without giving a proper memory, you will get the undefined result. Many new developers access pointers without allocating memory and frustrated with coming results. A pointer with no valid memory is called dangling pointers, for more deep knowledge you can read this article, dangling void null wild pointers.
Let see the below code,
#include<stdio.h> int main() { int *piData; //piData is dangling pointer *piData = 10; return 0; }
15.) Proper comments on the program:
I think it is good habits to write the comment in every section of the code. It always reminds you that what you did. It helps you if you read your code after some months or years.
I hope the article “common mistakes with memory allocation” helpful for you and I assumed you have learned the techniques to how to avoid common mistakes of memory allocation in C.
Recommended Articles for you:
- 10 interview questions on dynamic memory allocation.
- What are wild pointers in C and How can we avoid?
- What is a NULL pointer in C?
- Pointer Interview Questions in C/C++.
- Function pointer in c, a detailed guide
- How to create a dynamic array in C?
- How to access 2d array in C?
- A brief description of the pointer in C.
- Dangling, Void, Null and Wild Pointers
- Function pointer in c, a detailed guide
- How to use the structure of function pointer in c language?
- Memory Layout in C.
- 100 embedded C interview Questions.
- Python Interview Questions with Answer.
- File handling in C.
- Function pointer in structure.
- void pointer in C, A detailed discussion.
- 100 c interview questions, your interviewer might ask.
- C++ interview questions with answers.
- File handling in C.
- C format specifiers.