Arrays as Function Arguments in C

A function in C language can accept input parameters. These parameters can be of different types, such as integers, floats, chars, etc.

So far we have studied the case of simple types that are passed by copy. In this lesson, instead, we delve into the case where the parameters are arrays.

When passing an array as a function argument there are a variety of aspects to consider. In particular, there are differences compared to passing simple types.

Let's see what these differences are, first analyzing the case of one-dimensional arrays and then that of multidimensional arrays.

One-Dimensional Arrays as Function Arguments

In C language it is possible to define functions that have arrays as parameters and it is possible to pass arrays as arguments.

When a function parameter is a one-dimensional array, the length of the array can be omitted.

For example, we can write the following function definition:

int function(int a[]) {
    /* code */
}

In this case, we can pass to the function function any array of any length of the corresponding type which in the example is int.

For example, we can invoke the function in this way:

int array[5] = {1, 2, 3, 4, 5};

function(array);

At this point, however, a problem arises. How does the function know how many elements are in the array that was passed to it?

Unfortunately in C language there is no simple way to determine the length of an array passed as an argument to a function.

The sizeof operator can't help us in this case either. It can be used to calculate the size of an array variable, but it cannot be used to calculate the size of an array argument:

/* Works */
int array[5] = {1, 2, 3, 4, 5};

int size = sizeof(array) / sizeof(array[0]);
/* DOES NOT WORK !!! */
int function(int a[]) {
    int size = sizeof(a) / sizeof(a[0]);
}

To understand why we need to wait until we have studied pointers and their relationship with arrays. We will see this in the future.

So, in this case, how can we solve the problem?

The only way is to also pass the size of the array as an argument:

int function(int a[], int size) {
    /* code */
}

Let's see an example. Let's try to implement a sum function that takes as input an array of integers, its size and returns the sum of all elements of the array:

int sum(int a[], int size) {
    int sum = 0;

    for (int i = 0; i < size; i++) {
        sum += a[i];
    }

    return sum;
}

This function can be invoked in this way:

int array[5] = {1, 2, 3, 4, 5};

int result = sum(array, 5);

One important thing to note is that the array argument must not be followed by square brackets:

int result = sum(array, 5); // OK
int result = sum(array[], 5); // ERROR

We said that when we define a function that accepts an array as a parameter, the length of the array can be omitted. What happens, instead, if we specify it? Actually nothing.

The compiler, in fact, ignores the specified size:

int function(int a[5]) {
    /* code */
}

In this case, we can pass to the function function an array of any size. The compiler, in fact, will ignore the specified size and accept any array of the corresponding type.

int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

function(array); // OK

Why, then, specify the size of the array as a parameter? The only purpose is to make the code clearer, so that whoever reads the code knows that the array passed as an argument must have a specific size. In other words, specifying the size of an array parameter makes sense only for documentation purposes.

Summarizing:

Definition

One-Dimensional Arrays as Function Arguments

In C it is possible to use a one-dimensional array as a function parameter. The syntax is as follows:

type function(type array[], ...) {
    /* code */
}

The size of the array is not necessary nor is it checked by the compiler.

When invoking the function, the array passed as an argument must not be followed by square brackets.

type array[size] = {values};

function(array, ...);

The size of the array cannot be calculated by the function. If necessary, the size of the array must be passed as an argument.

type function(type array[], int size) {
    /* code */
}

Size Parameter of an Array

We have seen that the only way to let a function know the size of an array passed as an argument is to also pass its size.

At this point we wonder: how does the function in question understand that the size passed as an argument is correct?

The answer is very simple: it cannot.

Unfortunately, there is no way in C to check that the size passed as an argument is actually correct. If an incorrect size is passed, the function will not be able to do anything to prevent it.

Therefore, two cases can occur in which the size passed as an argument is not correct:

  1. The passed size is smaller than the actual size of the array.
  2. The passed size is greater than the actual size of the array.

In the first case, the function would work on a number of elements smaller than the actual one. This might not be a problem in certain cases and, indeed, it could be exploited to our advantage.

For example, returning to the example of the sum function, we can sum only the first n elements of the array in this way:

int a[] = {1, 2, 3, 4, 5};
int n = 3;

int result = sum(a, n);

In this case, the sum function will sum only the first 3 elements of array a, ignoring the last two. Indeed, the sum function in this case will not even know they exist.

In the second case, instead, the function would work on a number of elements greater than the actual one. This could be a problem, as the function could access memory areas not assigned to the array. A program crash could occur or anyway an incorrect result could be produced. In such case the behavior will not be predictable.

Note

Attention to the Size Parameter of an Array

Extreme caution must be exercised when passing the size of an array as an argument to a function. There is no way in C to check that the passed size is correct. If an incorrect size is passed, the program's behavior will not be predictable.

This can become particularly catastrophic when the passed size is greater than the actual size of the array.

Pass by Reference

In previous lessons we have seen that, by default, function arguments are passed by value. This means that an argument passed to a function is copied and the function works on a copy of the original argument.

This does not happen for arrays. They, in fact, are passed by reference.

This means that when an array is passed to a function, the function works directly on the original array and not on a copy of it. Therefore, any modification made to the array inside the function will be visible also outside the function. This detail is of fundamental importance.

Let's take an example. We want to create a function that increments by 1 all elements of an array of integers:

void increment(int a[], int size) {
    for (int i = 0; i < size; i++) {
        a[i]++;
    }
}

If we try to invoke the function in this way:

int array[5] = {1, 2, 3, 4, 5};

increment(array, 5);

After the function invocation, the array array will be modified in this way:

array = {2, 3, 4, 5, 6};

The fact that a function can modify the elements of an array seems to contradict the statement that all arguments are passed by copy. In reality, there is no contradiction. In fact a copy occurs when an array is passed to a function. The copy, however, is not the array itself, but the reference to the array. To understand this, however, we need to wait to study pointers and their relationship with arrays.

Definition

Arrays are passed by Reference to a function

Whenever an array is passed as an argument to a function, the function works directly on the original array and not on a copy of it. Any modification made to the array inside the function will be visible also outside the function.

This characteristic is called pass by reference.

In practice it is possible to use pass by reference also for other data types, such as integer types, float types, char types, etc. In the next lessons we will see how to do this using pointers.

Multidimensional Arrays as Function Arguments

In the previous section we have seen how to use one-dimensional arrays as function arguments.

We now wonder how we can pass multidimensional arrays as arguments.

In this case the situation becomes slightly more complicated. We have seen that the size of a one-dimensional array is ignored by the compiler when used as a function parameter. For multidimensional arrays, instead, we are obliged to always specify the last dimension.

In other words, if a function, for example, accepts a two-dimensional array of integers as an argument, we must always specify the second dimension, i.e. the number of columns.

Therefore, if we want to define a function that accepts a two-dimensional array of integers as an argument, the syntax will be as follows:

int function(int a[][5]) {
    /* code */
}

The consequence is that we can pass to the function two-dimensional arrays with an arbitrary number of rows but with a fixed number of columns.

If we try, in fact, to invoke the function using an array with a different number of columns we get a compilation error:

int array[2][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8}
};

function(array); // ERROR
Definition

Multidimensional Arrays as Function Arguments

A function can accept a multidimensional array as a parameter.

The constraint, in such case, is that the last dimension of the array must always be specified in the function definition. Moreover, the specified size must correspond to the actual size of the array passed as an argument.

The syntax to define a function that accepts a multidimensional array of dimension:

n_1 \times n_2 \times \ldots \times n_k

is as follows:

return_type function(type array[][]...[n_k]) {
    /* code */
}

Where n_k specifies the last dimension of the array.

Not being able to pass a multidimensional array with a number of columns different from that specified in the function definition can be a problem. Fortunately, this problem can be solved in C99 using a variable-length array as we will see in the next section.

Variable-Length Arrays as Function Arguments in C99

The C99 standard adds new features to the C language, including the ability to pass variable-length arrays as function arguments.

As we have seen in the related lesson, a variable-length array, or VLA for short, is an array whose size is determined at runtime through a constant expression.

In C99 we can, in fact, define an array in this way:

int n = 5;
int array[n];

In this case, the size of the array array is determined by the variable n whose value is known only at execution time.

We can exploit this characteristic to our advantage when we define functions that accept an array. Let's return to the example of the sum function that calculates the sum of all elements of an array of integers.

Originally, we had written the function in this way:

int sum(int a[], int size) {
    int sum = 0;

    for (int i = 0; i < size; i++) {
        sum += a[i];
    }

    return sum;
}

As can be observed, the defect of the function written in this way is that there is no relationship between the size parameter and the actual size of array a.

Even though the sum function uses the size parameter to determine the length of the array, there still exists the problem that it may not correspond to the actual size of the array. In other words, no one checks that the size passed as an argument is correct.

Using VLAs in C99 we can explicitly bind the size of the array to the size parameter so that the compiler checks that the size passed as an argument is correct. We can rewrite, in fact, the sum function in this way:

int sum(int size, int a[size]) {
    int sum = 0;

    for (int i = 0; i < size; i++) {
        sum += a[i];
    }

    return sum;
}

The first parameter size specifies the size of array a. The second parameter a is a VLA of size size. Note that we have reversed the order of the parameters. If we had written the function in this way:

int sum(int a[size], int size) {
    /* code */
}

The compiler would have generated a compilation error. This is because, when the compiler encounters int a[size], it doesn't yet know who size is.

Definition

Variable-Length Arrays as Function Arguments in C99 - One-Dimensional Case

In C99 it is possible to pass one-dimensional variable-length arrays as function arguments.

The syntax to define a function that accepts a variable-length array is as follows:

return_type function(int size, type array[size]) {
    /* code */
}

Where size specifies the size of array array.

Fundamental is the order of parameters. The size parameter must precede the variable-length array.

The C99 standard also provides a certain flexibility in the way we can declare function prototypes that accept VLAs.

Let's return to the example of the sum function. We can declare its prototype in three ways:

  1. We can declare the prototype in the same way we defined the function:

    int sum(int size, int a[size]);
    
  2. We can declare the prototype using an asterisk inside the square brackets:

    int sum(int size, int a[*]);
    

    In this case, the asterisk indicates that the size of array a is determined at runtime. The compiler anyway knows that a is a VLA and that its size depends on the size parameter because in the actual definition of the function a is declared as int a[size].

  3. The third way consists in not specifying any size leaving the square brackets empty:

    int sum(int size, int a[]);
    

    Also in this case, the compiler knows that a is a VLA and that its size depends on the size parameter from the definition.

    This third way, however, is discouraged because it does not explicitly document that a has a size dependent on size. Therefore, in the eyes of a developer who uses the sum function this dependency is not explicit.

The power of variable-length arrays lies in the fact that their size can be any constant expression. Therefore, we can also write more complex functions.

Suppose, for example, we want to create a function that concatenates two arrays into a third. We can create the function in this way:

void concatenate(int m, int n, int a[m], int b[n], int c[m + n]) {
    for (int i = 0; i < m; i++) {
        c[i] = a[i];
    }

    for (int i = 0; i < n; i++) {
        c[m + i] = b[i];
    }
}

In this case, the concatenate function accepts three variable-length arrays a, b and c. The size of array c is the sum of the sizes of arrays a and b specified in parameters m and n.

The use of variable-length arrays in the one-dimensional case does not have great utility. The compiler, effectively, checks that the size passed as an argument is correct, but in most cases what it does is emit a warning. In other words, it is still possible to pass a wrong size.

The program is compiled anyway, even if the size is wrong.

Let's say that in the one-dimensional case the advantage concerns the fact that our functions are better documented.

The real advantage of variable-length arrays concerns the multidimensional case.

Above we have seen that when we use multidimensional arrays as function arguments, we are obliged to always specify the last dimension. Moreover, we are limited to always passing arrays with a fixed number of columns. Using variable-length arrays we can overcome these limitations.

Suppose, for example, we want to create a function that sums all elements of an m x n matrix. We can create the function in this way in C99:

int sum(int m, int n, int a[m][n]) {
    int sum = 0;

    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            sum += a[i][j];
        }
    }

    return sum;
}

In this case, the sum function accepts an m x n matrix of integers. The size of the matrix is specified in parameters m and n and we can pass a matrix with an arbitrary number of rows and columns.

We can invoke the function in this way:

int matrix[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

int result = sum(2, 3, matrix);
Definition

Variable-Length Arrays as Function Arguments in C99 - Multidimensional Case

In C99 it is possible to pass multidimensional variable-length arrays as function arguments.

The syntax to define a function that accepts a multidimensional variable-length array is as follows:

return_type function(int m, int n, type array[m][n]) {
    /* code */
}

Where m and n specify respectively the number of rows and columns of matrix array.

Use of the static Keyword for Sizes in C99

The C99 standard also introduces an important novelty. We can use the static keyword to specify the size of an array.

The static keyword can be used before the size of the array. For example, we can write a function in this way:

int function(int n, int a[static 3]) {
    /* code */
}

In this way we are telling the compiler that we guarantee that the arrays that will be passed to the function will always have at least 3 elements.

In practice nothing changes. Our program will always work in the same way. The difference lies in the fact that the compiler can optimize the code more efficiently. In fact, static serves as a hint for the compiler.

The compiler could (or not) generate more efficient code if it knows that the size of the array is always at least 3. For example, it could preload into memory the first 3 elements of the array in order to access these elements more quickly. It is therefore an optimization hint. At the functionality level, instead, the behavior of the program does not change.

There is a limit, however, to the use of static to specify sizes. When multidimensional arrays are used as function arguments, static can be used only to specify the first dimension. For example:

int function(int m, int n, int a[static 4][n]) {
    /* code */
}
Definition

Use of the static Keyword for Sizes in C99

In C99 we can use the static keyword to specify the size of an array.

The static keyword can be used before the size of the array. For example:

return_type function(int n, type a[static m]) {
    /* code */
}

In this way we are telling the compiler that we guarantee that the arrays that will be passed to the function will always have at least m elements.

Anonymous Arrays in C99

Let's return to the example of the sum function to sum all elements of an array of integers.

Wanting to use the function to sum the elements of an array, we must write code similar to the following:

int array[5] = {1, 2, 3, 4, 5};

int result = sum(5, array);

This code works correctly. It only has the problem that we are always forced to define an array, initialize it and then pass it as an argument to the function.

If, for example, we are only interested in result but the array subsequently is no longer needed, we were forced to define a useless array used only to invoke sum.

We can avoid this problem in C99 by passing as an argument an anonymous array.

An anonymous array consists of an array without a name created on the fly at the point where it is needed. We can create an anonymous array in this way:

int result = sum(5, (int[]){1, 2, 3, 4, 5});

In this example, we have created an anonymous array of integers {1, 2, 3, 4, 5} and we have passed it as an argument to the sum function.

We have used the following syntax:

(int[]){1, 2, 3, 4, 5}

In general the syntax is composed of:

In general, the length of the array can be omitted as it is determined by the initialization list.

An anonymous array can also use designated initializers. For example, we can create an anonymous array in this way:

int result = sum(5, (int[]){[0] = 1, [2] = 3, [4] = 5});

Moreover, an initialization list can also be partial. In this case, however, it is necessary to specify the length of the array:

int result = sum(5, (int[10]){1, 2, [5] = 9});

In this case, the anonymous array has size 10 and the first two elements are initialized to 1 and 2 respectively. The sixth element is initialized to 9.

Definition

Anonymous Arrays in C99

In C99 we can create anonymous arrays, i.e. arrays without a name created on the fly at the point where they are needed.

An anonymous array can be created in this way:

(type[]){values}

Where type specifies the type of the array and values is the initialization list of the array.

An anonymous array is still passed by reference, so it can be modified by the function even though this doesn't make much sense as the anonymous array is no longer accessible after the function call. In fact, an anonymous array is an l-value therefore it can be modified.

To make explicit the fact that the array is read-only we can use the const keyword in the definition of the anonymous array. For example:

int result = sum(5, (const int[]){1, 2, 3, 4, 5});

In this way the compiler checks that the anonymous array is not modified inside the function.

In Summary

In this lesson we have examined in depth all aspects concerning passing arrays as function arguments in C language.

We have seen that:

  • In C it is possible to pass one-dimensional arrays as function arguments. The size of the array can be omitted.
  • The size of an array passed as an argument cannot be calculated by the function. If necessary, the size of the array must be passed as an argument.
  • Arrays are passed by reference to a function. Any modification made to the array inside the function will be visible also outside the function. In other words, when an array is passed to a function, the function works directly on the original array and not on a copy of it.
  • In C99 we can pass variable-length arrays as function arguments. The size of the array can be any constant expression.
  • In C99 we can use the static keyword to specify the size of an array. static serves as a hint for the compiler.
  • In C99 we can create anonymous arrays, i.e. arrays without a name created on the fly at the point where they are needed.

In the next lesson, instead, we will examine in depth the mechanism of return values of a function in C language.