C Dynamic Memory Management Tutorial

In this C tutorial we learn how to manage runtime memory allocation, deallocation and reallocation.

We cover compile time memory allocation and runtime memory allocation and learn how to use the malloc(), calloc(), realloc() and the free() functions.

What is memory allocation

When a program is running, it will reserve space in the user’s system memory for temporary data containers like variables. The compiler reserves the required bytes of memory based on the specific data type.

For example, an int will typically reserve 2 or 4 bytes in memory, depending on the compiler. This is known as memory allocation.

C supports two types of memory allocation:

  • Static memory allocation at compile time.
  • Dynamic memory allocation at runtime.

Static (compile time) allocation

Compile time memory allocation means we reserve space in memory for variables before the application runs. Therefore, we need to know exactly how many bytes will be required, like specifying how many elements an array will have.

Any variable that we declare outside of the main() function will occupy memory at compile time. Compile time allocation is done on the stack, not on the heap.

Reserving memory at compile time means we won’t have control over the allocated memory. The compiler will take care of memory management, which means we can’t increase, decrease or free up memory as we need.

As an example, let’s consider that we declare an array to accommodate 50 employees, but we only currently employ 30 people.

Example:
 int emp[50];

The array declaration above will reserve space in memory for 50 elements (50 * 4 bytes).

The first problem is that there is wastage. The space for 20 extra unused elements will be reserved in memory and we won’t be able to use it for other purposes.

It’s also less flexible because we can’t change the array size. We have no control over the allocation and deallocation so we can’t clear any bytes from memory when we don’t need them.

Dynamic (runtime) allocation

Runtime memory allocation means we reserve space in memory for variables while the program runs. Where compile time allocation is done on the stack, runtime allocation is done on the heap.

If we want full control over allocated memory, we should prefer runtime allocation above compile time allocation. We will be able to allocate, deallocate and reallocate memory when we need to, ensuring our program is as performant as it can be.

The C language doesn’t have a garbage collector to automatically clear unused memory at certain periods. Dynamic memory management allows us to release unused memory manually when we need to.

C provides us with four functions, defined in the stdlib.h file, to use for dynamic memory allocation.

  • malloc()
  • calloc()
  • realloc()
  • free()

To use these functions, we must include the stdlib.h file at the top of our document.

Example:
#include <stdio.h>
#include <stdlib.h>

int main()
{
    return 0;
}

In the example above we include the stdlib.h file at the top of the document below the stdio.h file.

How to malloc() a memory block

The malloc() function will allocate a specified amount of bytes in a single block of memory, and return a pointer. The pointer that’s returned will point to the newly allocated memory.

If there isn’t enough memory available, the function returns NULL.

Syntax:
 void *malloc(numberOfBytes);

The pointer that’s returned is a void pointer, which means be can convert (cast) it to any other type.

To cast it to another type, we prefix the function with the type we want to cast it to in parentheses. The new type will replace the void type so we don’t specify void.

Syntax:
 (castType*)malloc(numberOfBytes);

Let’s say we want to reserve 2 integers. Because we may not know how many bytes a type is, we can use the sizeof operator, and multiply it by 2.

Syntax:
 (int*)malloc(2 * sizeof(int));

The last thing we need is something to store the address in that’s returned from the function.

Syntax:
int *ptr;

ptr = (int*)malloc(2 * sizeof(int));

In the example above we store the address in a pointer called ptr.

Now let’s see a full example to allocate with malloc().

Example:
#include <stdio.h>
#include <stdlib.h>

int main()
{
    // allocate memory for 2 integers
    int *ptr = (int *)malloc(2 * sizeof(int));

    // NULL check
    if(ptr == NULL)
    {
        printf("Memory not allocated");
        exit(0); // kill the program
    }
    else
    {
        printf("%d bytes allocated at %u (%p)\n", 2*sizeof(int), ptr, ptr);
    }

    // release allocated memory
    free(ptr);

    return 0;
}

Let’s break down the example step by step:

  1. First, we allocate 2 integers with the malloc() function and store the address in a pointer called ptr.
  2. Then, we check if the returned value is NULL. If it is, kill the program, otherwise print a message to the console.
  3. Lastly, release the allocated memory with free().

How to calloc() multiple memory blocks

The calloc() function will allocate a specified amount of bytes in multiple blocks of memory, and return a pointer.

Similar to the malloc() function, the calloc() function will return a pointer to the allocated memory, and if there isn’t enough memory available, it will return NULL.

The calloc() function accepts two arguments:

  • Total blocks to allocate
  • Number of bytes to reserve per block
Syntax:
 void* calloc(numberOfBlocks, numberOfBytes);

Essentially, we can say that calloc() will reserve (numberOfBlocks * numberOfBytes) in bytes.

Other than the extra parameter, the calloc() function looks the same as the malloc() function.

Syntax:
int *ptr;

ptr = (int*)calloc(2, sizeof(int));
Example:
#include <stdio.h>
#include <stdlib.h>

int main()
{
    // allocate memory for 5 elements
    int *ptr = (int *)calloc(5, sizeof(int));

    // NULL check
    if(ptr == NULL)
    {
        printf("Memory not allocated");
        exit(0); // kill the program
    }

    for (int i = 0; i < 5; i++)
    {
        printf("Value %u, allocated at %u (%p)\n", *(ptr + i), ptr+i, ptr+i);
    }


    // release allocated memory
    free(ptr);

    return 0;
}

This time, we allocate 5 blocks in memory in the example above. Then, we loop through them and print the value in the location, as well as the addresses.

All memory blocks allocated with the calloc() function are initialized with 0.

How to realloc() space in memory

When we’re working with large amounts of data, the allocated memory may not be sufficient to store that data. In that case, we need to update the existing memory blocks with new sizes.

To reallocate memory blocks, we use the realloc() function. The function will resize allocated memory blocks.

If it can’t, it will move the allocated memory blocks to a new location.

Syntax:
 void *realloc(pointer, updatedMemorySize);

The pointer parameter is a pointer to the memory block of the previously allocated memory, and updatedMemorySize is the existing plus new size of the memory block.

The realloc() function can, like malloc and calloc, be casted into another type.

Syntax:
 (int *)realloc(pointer, updatedMemorySize);
Example:
#include <stdio.h>
#include <stdlib.h>

int main()
{
    // original allocation
    int n = 10;
    int *ptr = (int *)malloc(n * sizeof(int));
    printf("%d bytes allocated at %u (%p)\n", n*sizeof(int), ptr, ptr);

    // increase allocation
    n = 30;
    ptr = (int *)realloc(ptr, n * sizeof(int));
    printf("%d bytes allocated at %u (%p)\n", n*sizeof(int), ptr, ptr);

    // release allocated memory
    free(ptr);

    return 0;
}

In the example above, we increase the allocation on the same memory address.

It may or may not allocate new memory blocks. If memory is allocated at a new location, the data will be moved ensuring that there is no data loss.

How to free() allocated memory

C provides us with a built-in library function to release any unused memory. To clear unused memory, we use the free() function.

The free() function will clear the pointer by assigning NULL to it. If the pointer is already NULLed, the function will do nothing.

Syntax:
 free(pointer);

The function accepts any void pointer.

Example:
#include <stdio.h>
#include <stdlib.h>

int main()
{
    // allocate memory
    int n = 10;
    int *ptr = (int *)malloc(n * sizeof(int));
    printf("%d bytes allocated at %u (%p)\n", n*sizeof(int), ptr, ptr);

    // release allocated memory
    free(ptr);

    return 0;
}

We must keep track of pointers from malloc() and free them, only once. The system’s memory doesn’t know whether its allocated or not.

Further reading

For more information on the subject of dynamic memory management, please see the links below.

Summary: Points to remember

  • The reserves memory for variables at compile time, or at runtime.
  • Memory reserved at runtime is called dynamic memory allocation.
  • To reserve memory at runtime we use the malloc() or calloc() functions.
  • To adjust memory that’s already been allocated, we use the realloc() function.
  • The malloc, calloc and realloc functions can be casted to any other pointer type with (type*).
  • To release allocated memory we use the free() function.