Home > Technology > Computer Science > Programming > C++ > malloc and realloc functions in C / C++

malloc and realloc functions in C / C++

In a previous post – “Using pointers in C / C++” – I made a brief introduction regarding pointers in C. Now, I’m going to talk about the malloc and realloc functions.

Sometimes we need to work with dynamic arrays or other type of data structures where we need to use pointers. On this post I’m going to focus on dynamic arrays.

Let’s start with a simple example and let’s imagine we want to record daily average temperatures at a determined location. We want the temperatures rounded to integers, so we need to have an array of integer numbers to store the temperatures. Notice that we could think on having a fixed size array for 365 days, for instance. In this case, we could declare our array as:

    int t[365];

But, in this case, after recording temperatures for one year, the program reaches the limit of the array, and we couldn’t insert more temperatures. So, the better approach is to use a pointer:

    int *t;

Now, what’s the best solution for managing the memory allocation for the array? Let’s see the following solutions:

  1. Allocating one element at a time as we enter more values
  2. Allocating an initial capacity for x elements and increasing the capacity by x each time we reach the limit
  3. Starting with an initial capacity for x elements and doubling the capacity each time we reach the limit

The first solution seems to be the most comfortable one as it avoids the need of being testing the array’s capacity. So, if we are going to insert a new temperature, we just allocate memory for one more value and so on. However, it is not the most elegant approach as we are constantly asking the system to allocate more memory. The second solution seems to be more reasonable and the third solution is, normally, the most used one. But in the end, it might depend of the implementation’s goals and in the how the amount of information will increase.

For this example, I’m going to use the second solution and consider that the array will start with a capacity of 10 and that this capacity will be increasing by 10 whenever we need more space. So, we will have a variable called “index” that will be counting the inserted temperatures, a variable called “capacity” that will control the arrays’ capacity and the array of temperatures:

    int index = 0;
    int capacity = 10;
    int *temperatures = NULL;
    
    temperatures = (int *) malloc(sizeof(int)*capacity);

Note that, when we allocate memory for the array we use the following calculation: “sizeof(int)*capacity”. The function “sizeof” returns the size of a data type or structure. In this case, we could have typed 4 because it is how much an integer number occupies in memory. But we don’t want to memorize this things, so we use the function “sizeof”. Finally, that size is multiplied by the capacity and we will get how many bytes we need to allocate memory for the first 10 elements of our array. The function “malloc” will then allocate the memory and return a pointer for the first memory address for the array. Because the function “malloc” returns a generic pointer, we have to cast it to a pointer for the type “int” with (int *).



Let’s now take a look on how we should write the code to record a temperature and verify the capacity of the array:

    int t;
    printf("Enter a temperature: ");
    scanf("%d", &t);
    temperatures[index] = t;
    index++;
    if (index>=capacity) {
        capacity+=10;
        temperatures = (int *) realloc(temperatures, sizeof(int)*capacity);
    }

So, we read and store the capacity and we check if the index reaches the capacity of the array. If so, we need to allocate more memory and we should use the function “realloc”. Here, we have to use the pointer of the last memory allocation (which, in this case, will be the same pointer) in order to tell “realloc” where we want to reallocate more memory. Then, the function will return a new pointer for a new memory zone. The beginning address might be the same, but because the memory is dynamically managed by the system, it’s very likely that the function will receive a new memory address and it will copy the data from the previous allocated memory to the new one (thus the reason we have to pass the pointer of the last allocation as an argument). On the other hand, if we use “malloc” instead, we will receive a new memory allocation but we will lose all the previous recorded data!

And never forget that, when we use these functions, we need to release the allocated memory in the end of the program

    free(temperaturas);

Otherwise, the program will exit but the operating system will keep that allocated memory and won’t release it, creating the so called “memory leaks”!

Let’s now see the complete code:

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

int main() {
    
    int menu = -1;
    int index = 0;
    int capacity = 10;
    int *temperatures = NULL;
    
    temperatures = (int *) malloc(sizeof(int)*capacity);
    
    while (menu!=0) {
        
        printf("1 - Insert temperature\n");
        printf("2 - List temperatures\n");
        printf("3 - State of the array\n");
        printf("0 - Exit program\n");
        scanf("%d", &menu);
        
        switch (menu) {
            case 0:
                break;
            case 1:
                int t;
                printf("Enter a temperature: ");
                scanf("%d", &t);
                temperatures[index] = t;
                index++;
                if (index>=capacity) {
                    capacity+=10;
                    temperatures = (int *) realloc(temperatures, sizeof(int)*capacity);
                }
                break;
            case 2:
                for (int i=0; i<index; i++) {
                    printf("Temperature on day %d = %d\n", i, temperatures[i]);
                }
                break;
            case 3:
                printf("Inserted elements: %d\n", index);
                printf("Capacity of the array: %d\n", capacity);
                break;
            default:
                printf("Invalid option!");
                break;
        }
        
    }
    
    free(temperatures);
    
    return 0;
    
}

We now have a program that displays a menu where we can insert temperatures, list temperatures, get the state of the array, so we can see how many elements are inserted and the current capacity of the array, and to exit the program. Notice the easy way we can access each element! We access them exactly the same way we do in static arrays. And it’s pretty easy to understand why if we see the allocated memory as a contiguous set of bytes. To read a specific position, and because the compiler knows we are using integer numbers, when we type “temperatures[i]”, the compiler knows that it has to get the value in the address “i * 4” (remember that the size in memory for an integer is 4 bytes) plus the starting address of the allocated memory. This “i*4” is commonly called an offset as it represents an offset from the beginning of the allocated memory.



So, let’s imagine that the allocated memory starts at the address 1000 (which is to where the pointer points). Then, temperatures[0] will have its element stored at the address “0 * 4 + 1000 = 1000”, temperatures[1] at “1 * 4 + 1000 = 1004”, temperatures[2] at “2 * 4 + 1000 = 1008”, and so on…

Keep reading more about this subject in: “Dynamic arrays with structs in C++

About Carlos Santos

Frequency of master studies in Electrical and Computer Engineering. Freelancer developer (also works remotely): Websites, Web Applications (JAVA and PHP), J2SE/J2EE, C/C++ and Android. Private instructor and professional trainer in computer science and electrical engineering. Teaches in classrooms and remotely using Skype and Team Viewer. Interests: Music, audio, video, science, astronomy and mythology.

Leave a Reply

Your email address will not be published and it is optional.