According to C standard, there are four storage duration, static, thread (C11), automatic, and allocated. The storage duration determines the lifetime of the object. The lifetime is a time duration in which object is lives (storage is reserved) and retain the previously stored value. If we have tried to access the object outside of its lifetime, the behavior could be undefined.
When we are going to develop a desktop application then we assume that a lot of memory and resources are available but the scenario is different with an embedded application. In case of small embedded application we have only a limited amount of resources and memory, so need to be careful.
In this article, I shall not discuss the all the storage duration, only discuss the dynamic memory allocation and the library function that is used to allocate memory at runtime. There are also some advantage and disadvantage of dynamic memory allocation, so I shall also try to focus on that points.
In C language, when we compile the program than we don’t know how much of memory is required at runtime, it creates the issues at the time of running. For example, in server communication, we don’t know the exact size of the response data, so in that situation, we have two solutions one is that create a huge size of the buffer or the second one is that allocate the memory at runtime.
In the system, the stack grows (invoking of function) and shrinks (after destroying the stack frame that is allocated for the function) as the program executes, and it is also hard to predict the worst size of the stack at compile time. So it is not a good idea to create a huge size buffer in the stack because it might be cross the stack boundary. It can be the cause of undefined result or stack overflow.
There is another problem with stack allocated object is that it is destroyed after exiting the control from the function, so in case of event-based architecture that is very popular in an embedded system where we need to share the object between different calls create the problem.
So to resolve all the problem that is mentioned above, C language provides the option of dynamic memory allocation and a good think about is that it live throughout the program until programmer does not destroy it explicitly.
What is memory management functions in C?
In C language, there are a lot of library functions (malloc, calloc, or realloc,..) which are used to allocate memory dynamically. One of the problems with dynamically allocated memory is that it is not destroyed by the compiler itself that means it is the responsibility of the user to deallocate the allocated memory.
When we allocate the memory using the memory management function, they return a pointer to the allocated memory block and returned the pointer that points to the start address of the memory block. If there is no space available, the function will return a null pointer.
Note: According to C standard, if the size of the space requested is zero, the behavior is implementation-defined: either a null pointer is returned, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object.
The malloc function
void *malloc(size_t size);
- The malloc function allocates space for an object whose size is specified by size.
- The value of the allocated space is indeterminate.
- If there is no space available, the malloc function return NULL.
The calloc function
void *calloc(size_t nmemb, size_t object_size);
- The calloc function allocates space for an array of nmemb objects, each of whose size is object_size.
- Space is initialized to all bits zero.
- The calloc function returns either a null pointer or a pointer to the allocated space.
Note: If you don’t want to initialize the allocated memory with zero, It would be better to use malloc over calloc.
The realloc function
void *realloc(void *ptr, size_t size);
- The realloc function is different from the malloc and calloc, it deallocates the old object and allocates again with newly specified size. If the new size is lesser to the old size, the contents of the newly allocated memory will be same as prior but if any bytes in the newly created object goes beyond the old size, the values of the object will be indeterminate.
- If piData is the null pointer, the realloc behaves like the malloc function.
- If piData not pointing a dynamically allocated memory, the behavior of realloc is undefined.
- If piData is pointing a deallocated memory ( memory block has been deallocated), the behavior of realloc is undefined.
- The return value of the realloc function is the pointer to a new object, or a null pointer if the new object could not be allocated.
The free function
void free(void *ptr);
- The free function is used to free the dynamically allocated memory.
- If piData (arguments of free) is a null pointer, the free function does not perform anything.
- If piData (arguments of free) does not point to the memory that is allocated by the memory management function the behavior of free function would be undefined.
- 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.
How to allocate and deallocate the dynamic memory?
In C language dynamically allocated memory comes from the heap. If you will see the process control block (PCB) of any process, the direction of heap and stack are opposite. If you will allocate the huge amount of memory from the heap, the heap grows like the stack and might be crossed the boundary.
Whenever we require the dynamic memory, we need to create a pointer of required type and allocate the block of memory using the library function malloc, calloc…etc.
char *piBuffer = malloc( 5 *sizeof(char));
We have already discussed that dynamic memory is not destroyed by the compiler, after using the allocated memory we need to destroy it explicitly using the free or realloc library function either we will get the memory leak issues. The memory leak can affect the performance of your system.
free(piBuffer ); //Now pointer dangling pointer
piBuffer = NULL; //Now pointer is not more dangling
A brief description of dangling pointer see this: dangling pointer
Few important points need to remember
- Every block of memory that is allocated by malloc, calloc or any library function must be freed using the free or realloc function.
- The free function must be used with dynamically allocated memory.
- Do not use free function more than once for a single allocated memory.
Some disadvantage of dynamic memory
- We have already discussed that compiler does not deallocate the dynamically allocated memory, the developer needs to clear the allocated memory. If the developer forgets to free the allocated memory, it can cause the memory leak and makes your program slow.
int *piData = (int *) malloc(sizeof(int));
/* Do some work */
return 0; /*Not freeing the allocated memory*/
- The dynamic memory can be the cause of the memory fragmentation.
Below explanation is only for the sake of understanding. Suppose heap had a capacity for 4K of memory. If the user consumes 2K of memory, the available memory would be 2K.
char *pcData1 = malloc(512);
char *pcData2 = malloc(1024);
char *pcData3 = malloc(512);
When the user has deallocated the memory that is pointed by p2 than freed memory is available for the further use.
Now, 3K of memory is available but contiguous memory is only 2k. If the user tries to allocate 3K of memory, the allocation would fail, even 3K of memory is free.
- If you have allocated the memory dynamically, some extra bytes are wasted because it reserves a bookkeeping to put the information of the allocated memory. So dynamic memory is beneficial when you need to allocate a large amount of memory.
- If we do not use the dynamic properly (in the situation of shallow copy), it can cause of code crashing or unwanted result.
Let’s take an example,
int *piData1 = NULL;
int *piData2 = NULL;
piData1 = malloc(sizeof(int));
*piData1 = 100;
printf(" *piData1 = %d\n",*piData1);
piData2 = piData1;
printf(" *piData1 = %d\n",*piData2);
*piData2 = 50;
printf(" *piData2 = %d\n",*piData2);
In 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 of piData2.