Home

 › 

Articles

 › 

Dynamic Memory Allocation in C: How Does It Work?

Dynamic memory allocation

Dynamic Memory Allocation in C: How Does It Work?

When we’re working with arrays, we’ll often run into a situation where we need to adjust the size of the array. This could be increasing its size to add more elements, or decreasing its size to eliminate empty indices to save memory. This is where the concept of dynamic memory allocation comes into play. Read on for an overview of how this works and how to implement it in C.

What Is Dynamic Memory Allocation and Why Is It Important?

As briefly mentioned, dynamic memory allocation is the process of changing array size. This can be done with string arrays, but is mostly used with integer arrays. The helpful part about this is that it’s done during the runtime of the program. While called allocation, the process involves deallocating memory as well. This allows the program to request these changes as needed. Sometimes, we don’t know exactly how much memory we’ll need before we run a program. Dynamic memory allocation becomes very useful in this case.

Generally, dynamic memory allocation is in contrast to static memory allocation. As you may have guessed, static allocation cannot change the array size during runtime, only doing it at the time of compiling. Static allocation can also only be used for arrays, whereas dynamic allocation can be used for linked lists as well as tree structures.

What Types of Dynamic Memory Allocation Are There?

There are two main types of dynamic memory allocation – heap-based, and stack-based.

As the names suggest, heap-based uses memory allocated to a heap structure, while stack-based concerns memory allocated to a stack. Heap-based is a very common usage, such as for the structures previously mentioned, as well as hash tables. Stack-based, however, is generally used to allocate memory for functions, as well as construct temporary variables.

Although many programming languages can use manual memory allocation, some have a built-in function known as a “garbage collector” for automatically allocating memory. These include Python, Ruby and Java.

The Working Behind Dynamic Memory Allocation Functions

There are four functions used for dynamic memory allocation — malloc(), calloc(), free(), and realloc(). It’s time to jump into these and discover what they’re all about.

Malloc()

Standing for “memory allocation”, malloc() would be used when you need to allocate just one memory block to the program. A pointer is assigned which points to the first block of available memory. If the memory isn’t sufficient for this, a NULL value is returned by the function. The syntax of the function is given below.

void* malloc(size_t size);

where “void*” is a void type pointer, “malloc()” is the malloc() function, and “size” is the number of bytes of memory allocated.

Here is the code for malloc() implemented in C:

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

int main() {
    int num;
    int* ptr;

    printf("Enter the size of the dynamic array: ");
    scanf("%d", &num);

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

    if (ptr == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    for (int i = 0; i < num; i++) {
        printf("Enter element %d: ", i + 1);
        scanf("%d", &ptr[i]);
    }

    printf("The dynamic array is: ");
    for (int i = 0; i < num; i++) {
        printf("%d ", ptr[i]);
    }
    printf("\n");

    return 0;
}

Explanation of Code

First, we use include statements to include the standard library and input/output files. Then, we declare the “main()” function as the program’s entry point. Two variables are then declared — the integer “n”, the array size, and a pointer “arr”, used to point to the array.

After this, the user is prompted to enter the array size, and the value is read into “n.”

Next, we allocate memory to the array using the “malloc()” function. Bytes equivalent to “N * sizeof(int)” are allocated, and a pointer is returned that points to the first block. “(int*)” is used to convert the pointer to an integer value.

The next block of code checks if the allocation was successful. If the allocation returns NULL, it’s considered as failed as a value of 1 is returned.

The “for” loop next reads the elements and stores them in the array. The results of the allocation are then printed.

The final statement returns 0 after ending the “main” function. The results can be seen below, with element values entered as 4, 6, 8, 10 and 12.

Dynamic memory allocation
The malloc() function is used when you want to allocate one memory block to the program.

Calloc()

Calloc() is named as such as it’s short for contiguous memory allocation. While malloc() also allocates a continuous memory block, calloc() differs in that the memory allocated is initialized to 0. This is useful when dealing with sensitive data, such as passwords. Initializing to 0 ensures that no garbage data values are included which could compromise security. Calloc() is also helpful to save time when you don’t want to have to initialize each data field manually. The syntax for calloc() is as follows:

int* arr = (int*)calloc(5, sizeof(int));

where “int*” is the type of pointer value of the variable “arr.” “calloc()” is the calloc() function being declared, taking the number of elements argument and the size of each element. Namely, “5” is the element number and “sizeof(int)” is the size of each in bytes.

To implement calloc() in C, we can use the following code:

#include <stdio.h>
#include <stdlib.h>
  
int main()
{
    int* mem_block;
    int num_elements, i;
  
    num_elements = 5;
    printf("Enter number of elements: %d\n", num_elements);
  
    mem_block = (int*)calloc(num_elements, sizeof(int));
  
    if (mem_block == NULL) {
        printf("Memory not allocated.\n");
        exit(0);
    }
    else {
        printf("Memory successfully allocated using calloc.\n");
  
        for (i = 0; i < num_elements; ++i) {
            mem_block[i] = i + 1;
        }
  
        printf("The elements of the array are: ");
        for (i = 0; i < num_elements; ++i) {
            printf("%d, ", mem_block[i]);
        }
    }
  
    return 0;
}

Explanation of Code

As before, we include the standard header files. Then, we declare a “mem_block” pointer that stores the address of the allocated block, and the variables “i” and “num_elements.” After, we set the number of array elements to 5, which is then printed.

Memory is then allocated using “calloc()”, and the results stored in the pointer mentioned before. This is then checked, and, if failed, returns a status of 0. If successful, a verification message is printed. Each element is initialized using the counter variable “i”, so that the first element is set to 1, the second to 2, etc.

To finish, each element’s value is printed, and 0 is returned upon completion. The image below shows this code in action.

Dynamic memory allocation
The calloc() function is used when you are dealing with sensitive data like passwords.

Free()

Free() is a function used to deallocate memory, because the previous functions don’t do this automatically. To reduce memory wastage, using free() is usually essential. Free() has the following syntax:

void free(void* ptr);

where “ptr” is a pointer directed to the allocated block, “void” indicates that “free()” doesn’t return a value, and “void*” is the data of the pointer parameter.

To implement free() in C, we can use this code:

#include <stdio.h>
#include <stdlib.h>
  
int main()
{
  
    int *ptr, *ptr1;
    int num_elements, i;
  
    num_elements = 5;
    printf("Enter number of elements: %d\n", num_elements);
  
    ptr = (int*)malloc(num_elements * sizeof(int));
  
    ptr1 = (int*)calloc(num_elements, sizeof(int));
  
    if (ptr == NULL || ptr1 == NULL) {
        printf("Memory not allocated.\n");
        exit(0);
    }
    else {
  
        printf("Memory successfully allocated using malloc.\n");
  
        free(ptr);
        printf("Memory successfully freed.\n");
  
        printf("\nMemory successfully allocated using calloc.\n");
  
        free(ptr1);
        printf("Memory successfully freed.\n");
    }
  
    return 0;
}

Explanation of Code

We include the standard header files again, then we declare the pointer variable and integer variables. Then, the number of elements is read and printed, and memory is allocated using “malloc.” An “if” statement is used to check if the memory allocation was successful, and a message is printed if it failed. “n” integers are then stored in the allocated block and printed, at which point the memory is freed using the “free()” function.

More memory is allocated using “calloc()”, with the bytes initialized to 0. This operation is checked for success as before, and, if all was successful, success messages are printed using the “else” statement, and the program terminated. This is shown in the image below.

Dynamic memory allocation
The Free() function is used when you need to deallocate memory.

Realloc()

Realloc() is another function used for memory allocation, but this function is used to change, or “reallocate”, memory that was previously allocated. This is mostly used when the memory that was allocated is insufficient for the use case. The syntax for realloc() is:

void *realloc(void *ptr, size_t size);

where “ptr” is a pointer, “size” is the new size in bytes, “realloc()” is the realloc() function. “Void’ indicates no value is returned.

To implement realloc() in C, we can use the code below.

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

int main() {
    int* arr;
    int size, i;

    size = 5;
    printf("Enter initial size of the array: %d\n", size);

    arr = calloc(size, sizeof(int));

    if (arr == NULL) {
        printf("Memory not allocated.\n");
        exit(0);
    }
    else {
        printf("Memory successfully allocated using calloc.\n");

        for (i = 0; i < size; ++i) {
            arr[i] = i + 1;
        }

        printf("The elements of the array are: ");
        for (i = 0; i < size; ++i) {
            printf("%d, ", arr[i]);
        }

        size = 10;
        printf("\nEnter the new size of the array: %d\n", size);

        arr = realloc(arr, size * sizeof(int));

        printf("Memory successfully re-allocated using realloc.\n");

        for (i = 5; i < size; ++i) {
            arr[i] = i + 1;
        }

        printf("The elements of the array are: ");
        for (i = 0; i < size; ++i) {
            printf("%d, ", arr[i]);
        }
        
        free(arr);
    }

    return 0;
}

Explanation of Code

The standard header files are included, and the “main()” function is declared. The pointer variable is also declared, along with the “n” and “i” integer variables. “n” is initialized to 5, and the user is prompted to enter the number of elements here.

Memory is allocated using “calloc()”, and an “if” statement checks whether this was successful. Corresponding messages are printed to the console.

If the allocation was successful, as per the “else” statement, a “for” loop is used to traverse the array, assigning values to the ith element as it goes with “ptr[i] = i + 1.”

The next code block has another “for” loop, which prints the values of each element in the allocated memory block.

Then, the value of “n” is changed to 10, the memory is reallocated using “realloc()” and the new results are printed along with a success message.

Finally, the memory is freed using “free()”, and the program is terminated. This is all shown in the screenshots below.

Dynamic memory allocation
Dynamic memory allocation
The realloc() function is used when you need to reallocate memory.

Dynamic Memory Allocation: Wrapping Up

Overall, memory allocation functions are essential when manually allocating memory in languages that don’t have automatic garbage collection functions. Being able to dynamically change memory allocation during runtime is useful when the required memory is unknown, or memory is restricted and we need to free up as much as possible. To code efficiently, memory allocation is a very helpful tool in your arsenal.

Frequently Asked Questions

What is dynamic memory allocation?

Dynamic memory allocation is a method of allocating memory at a program’s runtime, and allows you to allocate, reallocate and release memory as required.

What is static memory allocation?

Static memory allocation refers to memory that’s allocated at the compile time, rather than during runtime. As such, the memory location is fixed throughout the running of the program, and memory is allocated using a stack. Static memory allocation is handled automatically be the compiler, and generally used where variables have a fixed size.

What are the advantages of dynamic memory allocation?

The process allows for flexibility and efficiency, as memory can be managed on an ad hoc basis. In this way, memory usage and storage can be optimized.

What are the limitations of dynamic memory allocation?

There is a possibility of memory leaks with dynamic memory allocation, as well as undesirable complexity and memory fragmentation.

What's the difference between heap memory and stack memory?

Stack memory is generally used for automatic memory allocation, where heap memory is allocated and deallocated explicitly using the functions described within this article. While stack memory is faster, heap memory offers superior flexibility, as well as accommodation of larger sizes.

What programming languages is dynamic memory allocation used with?

Several programming languages make use of dynamic memory allocation, including  C, C++, Ruby, Python and Java. Most languages make use of some sort of memory allocation, but the mechanisms and functions used with each language do vary.

What happens if memory allocation fails?

If dynamic memory allocations fails, usually a NULL value is returned and an error message displayed, the wording of which depends on your implementation.

What is a memory leak?

This occurs when the allocated memory isn’t released, which causes the program to have increasing memory usage over time. This can lead to a significant reduction in performance, and even system crashes. These can be avoided by releasing all memory once it’s no longer needed, as well as thoroughly testing programs for their efficiency.

How are memory leaks detected?

Memory leaks can be detected by using a debugger, or by tracking memory usage. Testing frameworks can also be used, as well as reviewing the code.

What is memory fragmentation?

This is when the memory available to a program is separated into many small blocks, meaning that it’s very difficult to allocate a larger block of memory to the program. By using functions which reduce fragmentation, i.e. calloc() and realloc(), and by defragmenting memory regularly, unnecessary fragmentation can be mitigated.

What are the most common functions used within dynamic memory allocation?

These are malloc(), calloc(), free() and realloc().

What's the difference between malloc() and calloc()?

Both of these functions allocate a memory block of a specified size, but calloc() initializes the bytes to 0. This is useful when we don’t want to manually initialize all blocks, and avoid segmentation errors. Calloc() also calculates the total memory allocated for you.

What is a segmentation error?

These occur when a program tries to access memory that it shouldn’t be allowed to access, such as unallocated memory or already freed memory. This can lead to unpredictable behavior. To avoid this, calloc() is often used to initialize bytes to 0, and checking return values for errors.

To top