Standard C language has 3 methods of memory management:
Static memory allocation:
This is usually allocated on the heap. Using this kind of allocation required fixed size objects. Static memory has little overhead. It is used for constants and global variables of fixed capacity.
Automatic memory allocation:
This is used to create transient objects defined in local scope of a function or block. These objects are stored on the stack and removed when the block terminate. It also required fixed size objects.
Dynamic memory allocation:
Sometimes we do not know how large an object will be until the program is running. When the size is known we can allocate memory using a function call with parameters. We need to calculate the memory required before we call memory allocation function.
After we use the object and is no longer necessary to keep, we can remove object from memory using a function call. If we free objects still in use the program can become unpredictable. Compilers usually do not detect these kind of errors.
Dynamic memory allocation has overhead. Therefore it can slow down the program. Sometimes the memory allocation can fail, so we have to check if the allocation was successful before we can setup and use the object we have created.
Dynamic memory allocation functions are found in <stdlib.h> header file. You must include this header into your application to be able to allocate and reallocate memory for dynamic objects. There are 4 important memory allocation functions:
Example: for malloc and free
#include <stdio.h>
#include <stdlib.h>
int main() {
// dynamic array allocation
int *array = malloc(10 * sizeof(int));
if (array == NULL) {
fprintf(stderr, "malloc failed\n");
return(-1);
}
// array initialization
int i = 0;
for (i = 0; i < 10; i++) {
printf("array[%d] = %d\n", i, array[i]);
}
free(array);
return(0);
}
Note: Observe that we do not assign any value to array elements. This is bad practice. Most of the time the value will be 0 and you may think the program is fine. However sometimes the elements may have undetermined value and should be initialized.
Example: for calloc() and realloc()
#include <stdio.h>
#include <stdlib.h>
int main() {
// dynamic array allocation
int *array = calloc(5, sizeof(int));
if (array == NULL) {
fprintf(stderr, "calloc failed\n");
return(-1);
}
// array traversal
int i = 0;
for (i = 0; i < 5; i++) {
printf("array[%d] = %d\n", i, array[i]);
}
// resize array
array = realloc(array, 10 * sizeof(int));
for (i = 0; i < 10; i++) {
printf("array[%d] = %d\n", i, array[i]);
}
// remove the array from memory
free(array);
return(0);
}
Note: realloc() like maloc() do not initialize the new array elements. So if you do not, the value is not predictable. It may be garbage. Therefore is a good practice to give value to new elements.
Sometimes we do not know how much memory to allocate, it can be users decision. In this case we must allocate memory after user input. In next example we organize a group of persons in memory using consecutive pointers.
#include <stdio.h>
#include <stdlib.h>
/* define structure person */
typedef struct {
int id;
char name[20];
int age;
} Person;
int main()
{
Person *p;
int i, n;
printf("Number of persons: ");
scanf("%d", &n);
/* dynamic allocation */
p = malloc(n * sizeof(Person));
for(i = 0; i < n; ++i)
{
(p+i)->id = i+1;
printf("ID: %d\n", (p+i)->id);
printf(" name:");
scanf("%s", &(p+i)->name);
printf(" age:");
scanf("%d", &(p+i)->age);
}
printf("\n");
printf("List of persons:\n");
for(i = 0; i < n; ++i)
printf("ID: %d Name: %-20s\tAge: %d\n", (p+i)->id, (p+i)->name, (p+i)->age);
return 0;
}
Note: At the end of program the memory is free automatically. Normally we should use free after we have used all the persons. For simplicity in this example we do not clean-up the memory.
Allocation failures
Memory allocation is not guaranteed to succeed, and may instead return a null pointer. Using the returned value, without checking if the allocation is successful, invokes undefined behavior. This usually leads to crash, but there is no guarantee that a crash will happen.
Memory leaks
Failure to free memory leads to buildup of non-reusable memory, which is no longer used by the program. This wastes memory resources and can lead to allocation failures when these resources are exhausted.
Logical errors
All allocations must follow the same pattern: allocation using malloc, usage to store data, de-allocation using free. Failure to implement this pattern can result in logical errors. Most common:
Read next: Error Handling