Dynamic Memory Allocation

In the old days of programming, arrays were always defined with a fixed, or static size. However, the amount of input to a program is rarely fixed. This means that static arrays must be defined to accommodate the largest possible inputs. For example, a program made to process lists of up to 1,000,000 elements must use an array of 1,000,000 elements, even when processing a list of 3 elements. This is a colossal waste of memory resources.

All modern languages allow the size of arrays to be determined at run-time, so they only use as much memory as needed. For this reason, static arrays should not be used anymore, unless it is an absolute certainty that the size of the array will not vary much.

More often than not, we don't know how big our list is until run-time.

In C, an array name is actually a pointer. The array name always represents the address of the first element in the array. The only difference between an array name and a pointer variable in C is that we cannot change what the array name points to. I.e., an array name is a pointer constant rather than a pointer variable.

Pointer variables and array names are completely interchangeable, with one exception: An array name by itself cannot be on the left side of an assignment statement. Most importantly, we can use subscripts with pointer variables just like we do with array names.

Hence, to create a dynamically allocated array in C, we begin by defining a pointer variable instead of an array variable.

We then use the malloc() library function to allocate memory, which is prototyped in stdlib.h. The malloc() function returns the address of the allocated memory, or the constant NULL if the allocation failed.

The return type of malloc() is void *, so we should cast it to the pointer type of the array to avoid a compiler warning.

Lastly, note that malloc() takes the number of bytes to allocate as an argument, not the number of array elements. With malloc(), we generally use the C sizeof() operator, which returns the size of a variable or data type.

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

int     main(int argc,char *argv[])

{
    double  *temperatures;
    size_t  num_temps,
            c;

    printf("How many temperatures are there? ");
    scanf("%zu", &num_temps);   // %zu for size_t
    printf("%zu\n", num_temps);
    temperatures = (double *)malloc(num_temps * sizeof(*temperatures));
    if ( temperatures == NULL )
    {
        fprintf(stderr, "Cannot allocate memory for temperatures.\n");
        exit(EX_OSERR);
    }
    
    puts("Enter the temperatures separated by whitespace:");
    for (c = 0; c < num_temps; ++c)
        scanf("%lf", &temperatures[c]);

    // Careful here: c is unsigned, so it is always >= 0
    while (c-- > 0)
        printf("%f\n", temperatures[c]);
    
    // Always free memory as soon as possible after it's used
    free(temperatures);
    return EX_OK;
}

In Fortran, we can indicate that the size of an array is to be determined later by adding the allocatable modifier and placing only a ':' in the dimension. We then use the allocate intrinsic subroutine to allocate the array at run-time.

program allocate
    use iso_fortran_env
    implicit none
    
    double precision, allocatable :: temperatures(:)
    integer :: num_temps, allocate_status, i
    
    print *, 'How many temperatures are there?'
    read *, num_temps
    allocate(temperatures(1:num_temps), stat=allocate_status)
    if ( allocate_status /= 0 ) then
        write(ERROR_UNIT, *) 'Cannot allocate memory for temperatures.', &
                            'Error code = ', allocate_status
        stop
    endif
    
    print *, 'Enter the temperatures one per line:'
    do i = 1, num_temps
        read *, temperatures(i)
    enddo
    
    do i = num_temps, 1, -1
        print *, temperatures(i)
    enddo
end program

We must provide an integer variable along with "stat=" to receive the status of the allocation attempt. If the memory for the array is allocated successfully, the status variable will be set to 0. If the allocation fails (there is not enough memory available to satisfy the request), it will be set to a non-zero value. Programs can be very sophisticated with the status codes returned. They may decide to request a smaller block, or deallocate something else and try again.

The very least we should do is stop the program. Failure to check the status of a memory allocation can cause incorrect output, which could be catastrophic in some cases.

Once the allocatable array is allocated, it can be used like any other array.

When the array is no longer needed, it should be immediately deallocated, to free up the memory for other uses.

deallocate(temperatures)
        

Garbage collection