Dynamic Memory Allocation

Introduction

Most of the memory you've been using has been stored on the stack. A stack is an area of memory where local variables and functions are stored. The variables are pushed onto the stack when called and are popped off when the stack returns.

The problem arises when you need memory at run time and that is where the heap comes in. The heap is an area of memory that stores data that will need to be available longer term. Here is where memory is allocated at run time.

When you declare a variable the memory is taken care of by the program. This is called automatic memory allocation. When you have the responsibility to take care of the memory yourself it is called dynamic memory allocation. But in what situation might you be in this position? This happens when you don't know the exact size of your data structure or the data structure depends on another variable in the program.

Dynamic memory allocation is mainly used with these functions:

  1. malloc() function

  2. free() function

  3. calloc function

  4. realloc function

Malloc function

The "malloc" or "memory allocation" function is useful in a situation where you need to ask for memory from your operating system. It allocates size bytes and returns a pointer to the allocated memory. The memory is not initialized.

Syntax:

#include <stdlib> /* Header file to access malloc() */
void *malloc(size_t size); /* function prototype */

Example:

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

/**
 * main - Entry point
 * 
 * Return: 0 (Success)
 */
 int main(void)
 {
    int n;
    char *array;

    n = 4;
    /* Dynamically allocte memory with malloc() */
    array = malloc(n * sizeof(char));
    /* Check if malloc fails */
    if (array == NULL)
        return (NULL);
    array[0] = 'w';
    array[1] = 'o';
    array[2] = 'w';
    array[3] = '\0';
    printf("%s\n", array); /* Prints wow */
    return (0);
 }

Note: You may find in some examples where the result of malloc is cast. It works in both. It may use this syntax:

char *array = (char *)malloc(sizeof(char));

At times, malloc fails. On error, malloc returns NULL. Always check its return value

Release allocated memory with free()

When memory is not needed anymore you must return it to the operating system by calling the function free. The free() function frees memory pointed to by ptr which must have been returned by a previous call to malloc(), realloc() or calloc(). If ptr is NULL no operation is performed.

Syntax:

#include <stdlib> /* Header file to access free() */
void free(void *ptr);  /* Function prototype */

Example:

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

/**
 * main - Entry point
 * 
 * Return: 0 (Success)
 */
 int main(void)
 {
    int n;
    char *array;

    n = 4;
    /* Dynamically allocte memory with malloc() */
    array = malloc(n * sizeof(char));
    /* Check if malloc fails */
    if (array == NULL)
        return (NULL);
    array[0] = 'w';
    array[1] = 'o';
    array[2] = 'w';
    array[3] = '\0';
    printf("%s\n", array); /* Prints wow */
    free(array); /* Free memory */
    return (0);
 }

Calloc function

The "calloc" or "contiguous allocation" function allocates memory for an array of elements of size bytes each and returns a pointer to the allocated memory. It works like malloc(), the only difference is the memory is initialized to 0 and it receives two arguments.

Syntax:

#include <stdlib> /* Header file to access calloc() */
void *calloc(size_t nmemb, size_t size); /* Function prototype */

Example:

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

/**
 * main - Entry point
 * 
 * Return: 0 (Success)
 */
 int main(void)
 {
    int n;
    char *array;

    n = 4;
    /* Dynamically allocte memory with calloc() */
    array = calloc(n, sizeof(char));
    /* Check if malloc fails */
    if (array == NULL)
        return (NULL);
    array[0] = 'w';
    array[1] = 'o';
    array[2] = 'w';
    array[3] = '\0';
    printf("%s\n", array); /* Prints wow */
    free(array); /* Free memory */
    return (0);
 }

Realloc function

The "realloc" or "re-allocation" function is used to change the memory allocation of a previously allocated memory. It returns NULL if the memory allocation fails.

Syntax:

#include <stdlib.h> /* Header file to access realloc() */
void *reallloc(void *ptr, size_t size); /* Function prototype */

Example:

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

/**
 * main - Entry point
 * 
 * Return: 0 (Success)
 */
 int main(void)
 {
    int n;
    char *array;

    n = 4;
    /* Dynamically allocte memory with malloc() */
    array = malloc(n * sizeof(char));
    /* Check if malloc fails */
    if (array == NULL)
        return (NULL);
    array[0] = 'w';
    array[1] = '0';
    array[2] = 'w';
    array[3] = '\0';
    printf("%s\n", array); /* Prints wow */
    /* Resize the allocated memory using realloc() */
    n = 6; /* New size */
    array = realloc(array, n * sizeof(char)); 
    /* Check if realloc fails */ 
    if (array == NULL)
        return 1;
    /* Add more characters to the array */
    array[4] = '!';
    array[5] = '\0'; 
    printf("%s\n", array); /* Prints wow! */
    free(array); /* Free memory */
    return (0);
 }

Conclusion

Dynamic memory allocation is a powerful tool in programming that enables the efficient use of memory resources during runtime. It provides flexibility and adaptability, allowing programs to handle varying data sizes and structures dynamically. However, with this great power comes great responsibility—developers must manage memory carefully to prevent memory leaks, segmentation faults, and other potential issues.

Understanding the basis of malloc, calloc, realloc, and free functions is crucial for effective memory management. It is essential to allocate memory appropriately, ensure proper initialization, and deallocate memory when it is no longer needed to maintain a healthy and efficient program.

Dynamic memory allocation is a double-edged sword, offering the freedom to adapt to changing circumstances but demanding vigilance and precision. As programmers, our ability to harness dynamic memory effectively contributes significantly to the reliability and performance of our applications. By striking a balance between flexibility and responsibility, we pave the way for robust and scalable software systems.

Happy coding!

References

Do I cast the result of malloc?