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 to which it points.

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 value stored in the parameter cannot change the value stored in its argument.

In the pass-by-pointer pattern, 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 (that is, 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 refer 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: an int pointer that 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 in the address of a variable as the argument:

    int x;
    change_value(&x);

    In the preceding 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(void) {
    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;, meaning "the location input points to gets the value 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).