2.3. Pointers and Functions

Pointer parameters provide a mechanism through which functions can modify argument values. The commonly used pass-by-pointer pattern uses a pointer function parameter that gets the value of the address of some storage location passed to it by the caller. For example, the caller could pass the address of one of its local variables. By dereferencing the pointer parameter inside the function, the function can modify the value at the storage location it points to.

We have already seen similar functionality with array parameters, where an array function parameter gets the value of the base address of the passed array (the parameter refers to the same set of array elements as its argument), and the function can modify the values stored in the array. In general, this same idea can be applied by passing pointer parameters to functions that point to the memory locations in the caller’s scope.

Pass by Value

All arguments in C are passed by value and follow pass by value semantics: the parameter gets a copy of its argument value, and modifying the parameter’s value does not change its argument’s value. When passing base type values, like the value of an int variable, the function parameter gets a copy of its argument value (the specific int value), and changing the int value stored in the parameter cannot change the int value stored in its argument.

In pass-by-pointer, the parameter still gets the value of its argument, but it is passed the value of an address. Just like in passing base types, changing a pointer parameter’s value will not change its argument’s value (i.e. assigning the parameter to point to a different address will not change the argument’s address value). However, by dereferencing a pointer parameter, the function can change the contents of memory that both the parameter and its argument refers to; through a pointer parameter, a function can modify a variable that is visible to the caller after the function returns.

Here are the steps for implementing and calling a function with a pass-by-pointer parameter, with example code snippets showing each step:

  1. Declare the function parameter to be a pointer to the variable type:

    /* input: is an int pointer: it stores the address of a memory
     *        location that can store an int value (it points to an int)
     */
    int change_value(int *input) {
  2. When making the function call, pass the address of a variable in as the argument:

    int x;
    change_value(&x);

    In the above example, since the parameter’s type is int *, the address passed must be the address of an int variable.

  3. In the body of the function, dereference the pointer parameter to change the argument’s value:

    *input = 100;  // the location input points to (x's memory) is assigned 100

Next, let’s examine a larger example program:

#include <stdio.h>

int change_value(int *input);

int main() {
    int x;
    int y;

    x = 30;
    y = change_value(&x);
    printf("x: %d y: %d\n", x, y);  // prints x: 100 y: 30

    return 0;
}

/*
 * changes the value of the argument
 *     input: a pointer to the value to change
 *     returns: the original value of the argument
 */
int change_value(int *input) {
    int val;

    val = *input; /* val gets the value input points to */

    if (val < 100) {
        *input = 100;  /* the value input points to gets 100 */
    } else {
        *input =  val * 2;
    }

    return val;
}

When run, the output is:

x: 100 y: 30

Figure 1 shows what the call stack looks like before executing the return in change_value:

The input parameter to change_value stores the address of main’s 'x' variable.
Figure 1. A snapshot of the call stack prior to returning from change_value.

The input parameter gets a copy of the value of its argument (the address of x). The value of x is 30 when the function call is made. Inside the change_value function, the parameter is dereferenced to assign the value 100 to the memory location pointed to by the parameter (*input = 100; — "the location input points to gets 100"). Since the parameter stores the address of a local variable in the main function’s stack frame, through dereferencing the parameter, the value stored in the caller’s local variable can be changed. When the function returns, the argument’s value reflects the change made to it through the pointer parameter (the value of x in main was changed to 100 by the change_value function through its input parameter).