munotes®

Pointers as Function Arguments: Call by Value and Call by Reference

Chapter Forty

Syllabus topic 3, "Pointer and Addresses, Pointer and Function Arguments, Pointer and Arrays."

Pages 194 to 198 of 222

In one line

To let a function change one of your variables, pass its address, and the function changes the object through the pointer.

The problem, restated

Chapter 33 proved that this cannot work:

void try_to_swap(int a, int b)
{
    int t = a; a = b; b = t;
}

a and b are local variables of try_to_swap, initialised from copies. The swap succeeds on the copies, which are then discarded.

The fix is not a different kind of parameter. It is a different kind of argument: instead of the value, pass where the value lives.

The practical: swap both ways

MU's Practical 7, with both methods in one program so the difference is visible in the output.

#include <stdio.h>

/* Call by value: the function gets copies. */
void swap_by_value(int a, int b)
{
    int t = a;
    a = b;
    b = t;
    printf("   inside swap_by_value : a is %d, b is %d\n", a, b);
}

/* Call by reference, as MU names it: the function gets addresses. */
void swap_by_reference(int *a, int *b)
{
    int t = *a;
    *a = *b;
    *b = t;
    printf("   inside swap_by_reference: *a is %d, *b is %d\n", *a, *b);
}

int main(void)
{
    int x = 10, y = 20;

    printf("before swap_by_value     : x is %d, y is %d\n", x, y);
    swap_by_value(x, y);
    printf("after  swap_by_value     : x is %d, y is %d   <- unchanged\n", x, y);

    printf("\nbefore swap_by_reference : x is %d, y is %d\n", x, y);
    swap_by_reference(&x, &y);
    printf("after  swap_by_reference : x is %d, y is %d   <- swapped\n", x, y);
    return 0;
}
before swap_by_value     : x is 10, y is 20
   inside swap_by_value : a is 20, b is 10
after  swap_by_value     : x is 10, y is 20   <- unchanged

before swap_by_reference : x is 10, y is 20
   inside swap_by_reference: *a is 20, *b is 10
after  swap_by_reference : x is 20, y is 10   <- swapped

Four lines of that program are the whole topic, and each is worth naming.

In the programWhat it is
void swap_by_reference(int a, int b)The parameters are pointers to int
swap_by_reference(&x, &y)The arguments are the addresses of x and y
int t = *a;Read the object a points at
a = b;Write the object a points at

Forget the & at the call and the compiler stops you, because an int is not an int . Forget a inside the function and you swap the two pointers instead of the two objects, which compiles and does nothing useful. That second mistake is the one to watch for.

#include <stdio.h>

void broken_swap(int *a, int *b)
{
    int *t = a;
    a = b;                  /* swaps the POINTERS, which are local copies */
    b = t;
    printf("   inside, *a is %d and *b is %d\n", *a, *b);
}

int main(void)
{
    int x = 10, y = 20;

    printf("before: x is %d, y is %d\n", x, y);
    broken_swap(&x, &y);
    printf("after : x is %d, y is %d   <- still unchanged\n", x, y);
    return 0;
}
munotes.in194

Pointers as Function Arguments: Call by Value and Call by Reference

before: x is 10, y is 20
   inside, *a is 20 and *b is 10
after : x is 10, y is 20   <- still unchanged

Note what did not happen: -Wall -Wextra said nothing at all about that function. It is a perfectly well formed program that assigns to two local variables, and there is nothing for a compiler to object to. Compare the listings in chapters 23, 24 and 26, where gcc does catch the mistake. The only way to catch this one is to read it.

The pointers themselves are parameters, and parameters are copies. Swapping them swaps two local variables, exactly as chapter 33's version swapped two local ints. The * is what reaches through to the caller's object.

Why "call by reference" is a borrowed name

C has one parameter-passing mechanism: the argument's value is copied into the parameter. When the argument is &x, the value copied is an address, and the function can then reach x through it.

So:

ArgumentParameter holdsCan change the caller's variable
Call by valuexa copy of x's valueNo
Call by reference, so called&xa copy of x's addressYes, through *

Both rows are call by value. The second passes a value that happens to be an address. Languages with genuine call by reference, such as C++ with int &a, let you write a = b in the function and change the caller's variable with no & at the call and no inside. C does not, and the & and you have to write are the visible evidence.

MU's paper uses "call by value" and "call by reference", so use her terms in an answer. Add the sentence "in C this is done by passing a pointer by value", which is correct and shows you know what the mechanism is.

The other reason to pass a pointer: more than one result

Chapter 33 said a function returns one value. A pointer parameter is the usual way round it.

#include <stdio.h>

/* Returns 1 on success, and writes the results through the pointers. */
int divide(int a, int b, int *quotient, int *remainder)
{
    if (b == 0) {
        return 0;
    }
    *quotient = a / b;
    *remainder = a % b;
    return 1;
}

void min_max(const int *a, int n, int *smallest, int *largest)
{
    *smallest = a[0];
    *largest = a[0];
    for (int i = 1; i < n; i++) {
        if (a[i] < *smallest) { *smallest = a[i]; }
        if (a[i] > *largest)  { *largest = a[i]; }
    }
}

int main(void)
{
    int q, r;

    if (divide(47, 5, &q, &r)) {
        printf("47 / 5 is %d remainder %d\n", q, r);
    }
    if (!divide(47, 0, &q, &r)) {
        printf("47 / 0 was refused, and q and r were left alone\n");
    }

    int a[] = {42, 8, 17, 4, 23};
    int lo, hi;
    min_max(a, 5, &lo, &hi);
    printf("smallest %d, largest %d\n", lo, hi);
    return 0;
}
munotes.in195

Pointers as Function Arguments: Call by Value and Call by Reference

47 / 5 is 9 remainder 2
47 / 0 was refused, and q and r were left alone
smallest 4, largest 42

That is the standard C shape for a function that can fail and also produce results: the return value says whether it worked, and the results come back through pointers. You have already used it: scanf returns how many items it read and puts the values where its pointer arguments say.

Which is why scanf takes &

scanf("%d", &n);

Now it is obvious. scanf has to put a value into your variable, and C copies arguments, so it cannot be given n: it would receive a copy and fill that in. It is given &n, the address, and writes through it.

And the exception you have already met: scanf("%s", name) for a character array takes no &, because an array argument is already an address. Chapter 41 is why.

Not writing through a pointer

Chapter 39 introduced const on a pointer parameter. It matters most here, where a reader has to know which arguments can come back changed.

void print_all(const int *a, int n);   /* will not change your array */
void double_all(int *a, int n);        /* will */
int  min_max(const int *a, int n, int *lo, int *hi);  /* reads a, writes lo and hi */

Read a function's parameter list and you can tell what it may do. That is worth more than any comment, because the compiler enforces it.

What goes wrong

A missing & at the call. The compiler catches it.

A missing * in the function. It may not catch it, as broken_swap showed.

Passing the address of something that has gone. Chapter 39's dangling pointer.

Not checking a pointer parameter for NULL. A function that dereferences a pointer it was given should either document that the pointer must not be null or test it.

#include <stdio.h>

int safe_double(int *n)
{
    if (n == NULL) {
        return 0;
    }
    *n = *n * 2;
    return 1;
}

int main(void)
{
    int x = 21;

    printf("safe_double(&x) returned %d and x is %d\n", safe_double(&x), x);
    printf("safe_double(NULL) returned %d, and nothing was touched\n",
           safe_double(NULL));
    return 0;
}
munotes.in196

Pointers as Function Arguments: Call by Value and Call by Reference

safe_double(&x) returned 1 and x is 42
safe_double(NULL) returned 0, and nothing was touched

What this does NOT mean

C does not have two parameter-passing modes. It has one, by value. Passing an address is a use of it.

A pointer parameter is not the caller's variable. It is a copy of the address, which is why swapping two pointer parameters changes nothing outside.

& at the call is not optional for a scalar. Without it the types do not match.

An array argument does not need &. It already yields an address.

Passing a pointer is not "faster". For an int it is the same cost or slightly more. For a large structure it is genuinely cheaper, which is the other reason to do it.

const on a pointer parameter is not a comment. The compiler refuses a write through it.

Quick revision

  • C passes every argument by value. Always.
  • To let a function change your variable, pass &x and use *p inside.
  • Parameters are int a; the call is f(&x); the body reads and writes a.
  • Swapping the pointers instead of the objects compiles and does nothing: the * is what matters.
  • "Call by reference" in C means passing a pointer by value. Say so, and use MU's term.
  • A function that must give back more than one result takes pointers for the extras, and returns whether it worked.
  • scanf takes &n for the same reason, and no & for an array because an array is already an address.
  • const int *a in a parameter list promises not to write through it, and the compiler enforces it.
  • Test a pointer parameter against NULL if the caller could reasonably pass one.

Test yourself

1. Why can a function taking two int parameters not swap the caller's variables?

Because the arguments are copied into the parameters, which are local variables. The function swaps its own copies and they are discarded when it returns.

2. Write a swap function that works, and its call.

void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }
swap(&x, &y);

3. Does C have call by reference?

No. C has call by value only. What is called call by reference in C is passing a pointer, which is itself passed by value; the & at the call and the * in the function are the evidence.

4. What does this function do to the caller's variables?

void f(int *a, int *b) { int *t = a; a = b; b = t; }
munotes.in197

Pointers as Function Arguments: Call by Value and Call by Reference

Nothing. It swaps two local pointer variables. The objects they point at are untouched.

5. Why does scanf need &n but not &name for a character array?

scanf must write into your object, and C copies arguments, so it needs the address. An array used as an argument already yields the address of its first element, so no & is needed or wanted.

6. How does a function return two results?

By taking a pointer for each extra result and writing through them, usually with the return value reporting success. Returning a structure is the other way.

7. What does const add to void print_all(const int *a, int n)?

A compiler-checked promise that the function will not write through a, so the caller knows the array comes back unchanged.

What can be asked on this, and how to answer it

"Write a program to swap two numbers using call by value and call by reference." Give this chapter's program with both functions and the output showing that one worked and the other did not. Then add the sentence that earns the extra mark: C has only call by value, and the second method passes a pointer by value.

"Distinguish between call by value and call by reference." Give the table: the argument is the value against the address, the parameter holds a copy of the value against a copy of the address, and the function cannot against can change the caller's variable. Note that in C both are call by value, and that a genuine reference parameter exists in C++ and not in C.

"Why does scanf require the address of a variable?" Because arguments are copied, so scanf given the value could only fill in its own copy. Given the address it writes into the caller's object. Add that an array argument already yields an address, so %s needs no &.

"How can a function return more than one value?" By taking pointers to the caller's variables and writing the extra results through them, or by returning a structure. Give the divide function with its quotient, remainder and success return.

"What is the output of this program?" with a swap that swaps pointers rather than objects. Say that the parameters are local copies of the addresses, so swapping them changes nothing in the caller, and give the unchanged values.

munotes.in198

The rest of this subject

These notes are cut from the University's printed syllabus. Open the syllabus itself for the same subject.

Report or request
Done!