Dynamic Array Allocation in C

Let's expand our study on dynamic memory allocation in C, this time focusing on arrays.

So far, we have worked with static arrays, that is, arrays whose size is known at compile time or remains fixed during program execution.

Thanks to dynamic allocation, we can create arrays whose size can be calculated during program execution. Moreover, we can modify the size of a dynamic array at any time.

Dynamically allocated arrays

Dynamically allocated arrays have the same advantages as dynamically allocated strings, but with the difference that they can contain any type of data, not just characters.

When developing a program, it is often difficult to estimate in advance the correct size of an array. It is better to let the program itself, during execution, calculate the correct size and modify it accordingly.

In C language, this problem can be solved by dynamically allocating arrays and accessing them through a pointer to the first element.

The close relationship that exists between pointers and arrays in C makes the use of dynamic arrays simple and natural, just as if we were working with static arrays.

In general, to allocate an array dynamically we can use the malloc function. However, it is often convenient to use the calloc function for a number of advantages, in addition to the fact that it initializes all array elements to zero.

Instead, to dynamically modify the size of an array, that is, to make it grow or shrink during program execution, we can use the realloc function.

Now let's see these three functions in action for working with dynamic arrays.

Using malloc to allocate a dynamic array

The use of malloc to dynamically allocate an array works exactly as it does for strings. The main difference consists in the fact that the elements of an arbitrary array do not necessarily occupy one byte of memory as, instead, happens for strings.

For this reason, we must use the sizeof operator to calculate the amount of memory space needed for each element.

Let's clarify through an example. Suppose we want to allocate an array composed of n integers. The value n will be calculated during program execution.

First, we need to declare a pointer to integer:

int *array;

Once the value of n becomes known, we can dynamically allocate the array. However, the size will not be n, but rather n * sizeof(int). This is because each element of the array is an integer, which occupies sizeof(int) bytes of memory.

array = (int *) malloc(n * sizeof(int));

In general, given an array composed of elements of any type type, the formula to dynamically allocate an array of n elements is as follows:

type *array = (type *) malloc(n * sizeof(type));

This formula is very important. In fact, passing a wrong size to the malloc function could have disastrous consequences and cause segmentation fault.

Note

Be careful about the memory size requested from the malloc function

You must always use the sizeof operator to calculate the correct size of a dynamic array.

Getting the size of a dynamic array wrong can cause serious and unpredictable errors.

For example, if we want to allocate an array of n integers but write the following code:

int *array = (int *) malloc(n);

If the size of an int is greater than one byte, as happens practically on every modern computer, the malloc function will allocate an insufficient amount of memory. This could cause a segmentation fault or unpredictable behavior.

Once the pointer points to the allocated memory block, we can effectively ignore the fact that array is a pointer and use it as the name of an array. This is precisely by virtue of the fact that there is a close relationship between pointers and the name of an array.

For example, wanting to initialize all the elements of the array to zero, we can write:

for (int i = 0; i < n; i++) {
    array[i] = 0;
}

Furthermore, it is possible to use pointer arithmetic to access the elements of the array. For example, to print all the elements of the array, we can write:

int *p = array;
for (int i = 0; i < n; i++) {
    printf("%d ", *p);
    p++;
}
Definition

Using malloc to allocate an array

To dynamically allocate an array of n elements of type type, we can use the malloc function.

The general syntax is as follows:

type *array = (type *) malloc(n * sizeof(type));

where:

  • type is the data type of the array elements;
  • array is the pointer to the allocated array;
  • n is the number of elements in the array.

The calloc function and dynamic arrays

The malloc function has the advantage of being generic enough to be used in the vast majority of cases to allocate memory blocks that can contain any type of data.

In the case of arrays, however, it is often convenient to use the calloc function.

The calloc function is defined in the <stdlib.h> header of the C standard library and has the following prototype:

void *calloc(size_t num, size_t size);

The calloc function allocates the space needed to allocate an array composed of num elements, each of which occupies size bytes of memory.

In case of success, the function returns a pointer to the allocated memory block. In case of error, it returns NULL.

Furthermore, after allocating the requested memory, the calloc function initializes all the elements of the array to zero.

So, wanting for example to allocate an array of n integers and initialize them to zero, we can write:

int *array = (int *) calloc(n, sizeof(int));

To recap:

Definition

Using calloc to allocate and initialize an array

To dynamically allocate and initialize the elements of an array to zero, we can use the calloc function.

The general syntax is as follows:

type *array = (type *) calloc(n, sizeof(type));

where:

  • type is the data type of the array elements;
  • array is the pointer to the allocated array;
  • n is the number of elements in the array.

Dynamic modification of array size with realloc

When memory is allocated for an array, it might happen that, at a later time, the need arises to modify the size of the array. Perhaps, because it became necessary to add or remove elements.

For this purpose, the C standard library provides a library function called realloc. The realloc function can resize an array, and in general any dynamically allocated memory block, in such a way as to satisfy our needs.

The realloc function is defined also in the <stdlib.h> header and has the following prototype:

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

The realloc function takes as input a pointer ptr to a memory block previously allocated dynamically and the new size size that we want to assign to the memory block.

The fundamental point is that the pointer ptr must have been returned by a previous call to malloc, calloc or realloc. Otherwise, the behavior of the realloc function is undefined.

The parameter size, instead, can be larger or smaller than the original size of the memory block.

In general, realloc does not require that the memory block to be resized be an array. In fact, we can use realloc to resize any dynamically allocated memory block. In most cases, however, realloc is used to resize arrays.

Definition

Using realloc to resize an array

To dynamically resize an array, we can use the realloc function.

The general syntax is as follows:

array = (type *) realloc(array, new_size * sizeof(type));

where:

  • p is the pointer to the array to be resized;
  • type is the data type of the array elements;
  • new_size is the new size of the array.
Note

The pointer passed to realloc must have been returned by malloc, calloc or realloc

Care must be taken to pass to realloc a pointer that has been returned by a previous call to malloc, calloc or realloc.

If the pointer passed to realloc has not been returned by one of these functions, the behavior of the realloc function is undefined.

Rules of realloc behavior

The C language standard requires that the realloc function behave respecting the following rules:

Definition

Operating rules of the realloc function

  1. When realloc expands a memory block, the function does not initialize the bytes that make up the additional memory block. This means that the additional bytes could contain random values.
  2. If the realloc function fails, it returns NULL and leaves the original memory block unchanged.
  3. If the pointer passed as an argument to realloc is NULL, the function behaves like malloc and allocates a new memory block.
  4. If the requested size is zero, the realloc function behaves like free and frees the memory block.

The C language standard stops here and does not further specify the behavior of the realloc function.

However, one can still expect reasonably efficient behavior from the function.

In general, all implementations of the realloc function try to resize the size of the block in place.

In other words:

  • If the block must be shrunk, the realloc function will try to reduce the size of the memory block without having to move it;
  • If the block must be expanded, the realloc function will try to expand the memory block without having to move it.
  • If, in expanding the memory block there is not enough space, the realloc function will allocate a new memory block, copy the data from the old block to the new block and free the old block.

Using realloc

A key point regarding the use of the realloc function is that the dynamically allocated memory pointer passed as an argument must be updated with the value returned by the function.

For example:

/* We initially allocate an array of n elements */
int *array = (int *) malloc(n * sizeof(int));

/* ... */

/* We resize the array to m elements */
array = (int *) realloc(array, m * sizeof(int));

In this example, the array is resized from n to m elements.

Since the new memory block could be located at a completely different address compared to the original memory block, it is necessary to update the pointer array with the value returned by realloc.

For this reason, in the last line of the example, we assign the value returned by realloc to array.

Definition

The pointer passed to realloc must be updated with the returned value

After calling the realloc function, the pointer passed as an argument must be updated with the value returned by the function:

p = (type *) realloc(p, new_size * sizeof(type));

Deallocating the memory of a dynamic array

Just as for strings, also for generic dynamic arrays it is necessary to deallocate the memory once it is no longer needed.

To deallocate the memory of a dynamic array, we can use the free function as already seen previously:

free(array);

After calling free, the pointer array will no longer point to any memory block. Therefore, it is good practice to assign NULL to the pointer array to prevent it from being used erroneously:

array = NULL;

To the free function it is possible to pass any pointer obtained with a call to malloc, calloc or realloc.

In Summary

In this lesson we learned the following key points:

  • Dynamically allocated arrays in C are arrays that can grow or shrink during program execution.
  • To dynamically allocate an array, we can use the malloc or calloc function.
  • If we use malloc, we must calculate the correct size of the array by multiplying the number of elements by the size of each element:

    type *array = (type *) malloc(n * sizeof(type));
    
  • If we use calloc, the function initializes all the elements of the array to zero:

    type *array = (type *) calloc(n, sizeof(type));
    
  • To dynamically resize an array, we can use the realloc function:

    array = (type *) realloc(array, new_size * sizeof(type));
    
  • After calling realloc, the pointer passed as an argument must be updated with the value returned by the function.

  • To deallocate the memory of a dynamic array, we can use the free function:

    free(array);