Pointers as Function Arguments in C

The first method of using pointers in C language is to pass them as function arguments: passing parameters by reference.

Using this technique, it is possible to pass to a function the address of a variable and, in the function body, modify the content of the variable itself.

Passing Pointers as Function Arguments

We have seen in the lessons on functions in C language that the passing of variables and values occurs by copy.

In other words, when we pass a variable as a function argument the compiler provides to create a copy of the variable that will be used internally to the function.

For example, let's take the following function:

int test(int a, int b) {
    a = a - 2;
    return b * a;
}

This function takes as input two integers, a and b, and returns the result of (a - 2) \cdot b.

If we invoke the function in this way:

int x = 4;
int y = 6;
int z;

z = test(x, y);

We have that at the moment of invocation a will be a copy of x. Reason for which, when the line a = a - 2 is executed, the content of x will not be modified.

This behavior allows to protect the arguments passed to functions from possible modifications. However, it limits the application possibilities of functions.

Suppose we want to write a program that takes as input an angle expressed in degrees with decimal part, for example 132.453°, and that prints as output the same angle but expressed in degrees, minutes and seconds, for example 132° \, 27' \, 10.8''.

Let's start writing the skeleton of the program in this way:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <stdio.h>

int main() {
    double angle = 0.0;
    int degrees = 0;
    int minutes = 0;
    int seconds = 0;

    printf("Insert the angle: ");
    scanf("%lf", &angle);

    /* Processing */
    /* ... */

    printf("The result is: %d° %d' %d''\n", degrees, minutes, seconds);
    return 0;
}

We need to fill lines 12 and 13 with the necessary instructions for the calculation of the variables degrees, minutes and seconds starting from the variable angle.

We could think of writing a function, for example convert_angle but we have a problem. A function can return only one return value, that is, only one result. In our case, instead, the results are three.

One could decide to write three functions, each one to calculate degrees, minutes and seconds respectively. Or one can use pointers.

In fact, we can write a function that calculates the three variables by passing to it the references, that is the addresses, of the three variables to three pointers. The function signature would become:

void convert_angle(double angle, int *degrees, int *minutes, int *seconds);

At this point we can modify the program in this way:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <stdio.h>

int main() {
    double angle = 0.0;
    int degrees = 0;
    int minutes = 0;
    int seconds = 0;

    printf("Insert the angle: ");
    scanf("%lf", &angle);

    convert_angle(angle, &degrees, &minutes, &seconds);

    printf("The result is: %d° %d' %d''\n", degrees, minutes, seconds);
    return 0;
}

In line 12 we are invoking the function convert_angle passing as argument the addresses of the three variables degrees, minutes and seconds. At this point, in the function body the three pointer parameters will be aliases in all respects of the argument variables.

Therefore:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <math.h>

void integer_and_fractional_part(double value,
        double *integer_part,
        double *fractional_part) {
    *integer_part = floor(value);
    *fractional_part = value - *integer_part;
}

void convert_angle(double angle, int *degrees, int *minutes, int *seconds) {
    double integer_part;
    double fractional_part;

    integer_and_fractional_part(angle, &integer_part, &fractional_part);

    *degrees = (int) integer_part;

    angle = fractional_part * 60.0;

    integer_and_fractional_part(angle, &integer_part, &fractional_part);

    *minutes = (int) integer_part;

    angle = fractional_part * 60.0;

    integer_and_fractional_part(angle, &integer_part, &fractional_part);

    *seconds = (int) integer_part;
}

int main() {
    double angle = 0.0;
    int degrees = 0;
    int minutes = 0;
    int seconds = 0;

    printf("Insert the angle: ");
    scanf("%lf", &angle);

    convert_angle(angle, &degrees, &minutes, &seconds);

    printf("The result is: %d° %d' %d''\n", degrees, minutes, seconds);
    return 0;
}

In the code above, the parameters degrees, minutes and seconds are pointers to integers. Passing to the function, in line 41 the addresses of the variables we have that, once the execution of the function is finished, such variables will contain the final calculated value. In other words, with this technique we have obtained multiple return values.

A possible execution of the program is the following:

Insert the angle: 132.453
The result is: 132° 27' 10''

Through pointers it is therefore possible to pass the arguments of a function by reference.

Definition

Passing arguments by reference

Through the use of pointers it is possible to pass the arguments of a function by reference. In other words, passing a pointer as a function argument one passes the address of the data to which the function itself can access. In this way the data can be modified in the function body.

This technique is especially useful when one wants to implement a function that returns more than one result.

The passing of pointers as function argument is actually not a new thing. In fact we have already used it when we used the scanf function.

For example, supposing we want to read an integer from console we can write the following code:

int x;
scanf("%d", &x);

When we use the scanf function we must always pass the address of the destination variable that will contain the value read from command line. For this reason, in the example above we used the address operator in front of the variable x when invoking the scanf function.

Note

Attention to passing pointers as function argument

When using pointers in passing arguments to function one must pay maximum attention. In fact, passing an argument that is not a pointer to a function that expects one could have unpredictable consequences.

Returning to the example of the conversion of an angle in degrees we have the function convert_angle whose signature is the following:

void convert_angle(double angle, int *degrees, int *minutes, int *seconds)

The function expects three pointers to integer as second, third and fourth parameter. Trying to invoke the function at line 41 in this way, that is without passing the address of the three variables but passing the value itself:

convert_angle(angle, degrees, minutes, seconds);

The function, in such case, would try to save the value of degrees, minutes and seconds in the address indicated by degrees, minutes and seconds. It would try, that is, to modify the content of the memory pointed by the values of degrees, minutes and seconds. Such locations are unknown and the consequences could be disastrous.

In general, C compilers, faced with such situations, provide warning messages. For example, the gcc compiler returns the following message:

convert_angle.c: In function 'main':
convert_angle.c:39:29: warning: passing argument 2 of 'convert_angle' makes pointer from integer without a cast [-Wint-conversion]
   39 |     convert_angle(angle, degrees, minutes, seconds);
      |                             ^~~~~~~
      |                             |
      |                             int
convert_angle.c:9:42: note: expected 'int *' but argument is of type 'int'
    9 | void convert_angle(double angle, int *degrees, int *minutes, int *seconds) {
      |                                     ~~~~~^~~~~~~
convert_angle.c:39:36: warning: passing argument 3 of 'convert_angle' makes pointer from integer without a cast [-Wint-conversion]
   39 |     convert_angle(angle, degrees, minutes, seconds);
      |                                    ^~~~~~~
      |                                    |
      |                                    int
convert_angle.c:9:54: note: expected 'int *' but argument is of type 'int'
    9 | void convert_angle(double angle, int *degrees, int *minutes, int *seconds) {
      |                                                 ~~~~~^~~~~~~
convert_angle.c:39:43: warning: passing argument 4 of 'convert_angle' makes pointer from integer without a cast [-Wint-conversion]
   39 |     convert_angle(angle, degrees, minutes, seconds);
      |                                           ^~~~~~~~
      |                                           |
      |                                           int
convert_angle.c:9:66: note: expected 'int *' but argument is of type 'int'
    9 | void convert_angle(double angle, int *degrees, int *minutes, int *seconds) {
      |  

The compiler compiles our program anyway, but still provides warning messages. Therefore it is always necessary to pay attention to this type of messages.

Example: finding the maximum element and the minimum element of an array

Let's look, now, at a practical example of using pointers as function arguments. We implement a function that determines the maximum and the minimum element of an array of integers.

This function, which we will call minimum_maximum, takes as input:

  • An array of integers: int a[];
  • The number of elements of the array int n;
  • A pointer to integer: int *min;
  • A pointer to integer: int *max.

The function finds the maximum and the minimum and stores the two results in the two memory locations pointed by max and by min.

A possible implementation of this function can be the following:

void minimum_maximum(int a[], int n, int *min, int *max) {
    int i;

    /* Initialize the minimum with the first element of the array */
    *min = a[0];

    /* Initialize the maximum with the first element of the array */
    *max = a[0];

    /* Start from the second element of the array */
    for (i = 1; i < n; ++i) {
        if (a[i] < *min) {
            *min = a[i];
        }
        if (a[i] > *max) {
            *max = a[i];
        }
    }
}

Let's try to write a complete program that reads 10 numbers from keyboard and finds the minimum and the maximum:

#include <stdio.h>

void minimum_maximum(int a[], int n, int *min, int *max);

int main() {
    const int n = 10;
    int a[n];
    int i;
    int min, max;

    printf("Insert 10 numbers:\n");

    for (i = 0; i < n; ++i) {
        scanf("%d", &a[i]);
    }

    minimum_maximum(a, n, &min, &max);

    printf("Minimum element:  %d\n", min);
    printf("Maximum element: %d\n", max);

    return 0;
}

void minimum_maximum(int a[], int n, int *min, int *max) {
    int i;

    /* Initialize the minimum with the first element of the array */
    *min = a[0];

    /* Initialize the maximum with the first element of the array */
    *max = a[0];

    /* Start from the second element of the array */
    for (i = 1; i < n; ++i) {
        if (a[i] < *min) {
            *min = a[i];
        }
        if (a[i] > *max) {
            *max = a[i];
        }
    }
}

Executing the program we obtain the following result:

Insert 10 numbers:
56
82
-12
92
124
31
73
47
56
-34
18
Minimum element:  -34
Maximum element: 124

Problems

Below are some problems to consolidate the concepts just seen:

In Summary

In this lesson we have seen how to pass a pointer as a function argument. This allows us to pass arguments by reference.

In this way, a function can modify the value of a variable that is defined outside the function.

In the next lesson we will see how to return a pointer as a return value of a function.