Kodewick
Lesson 16 of 30Contents

Pointers and functions

Lesson 16 of 30 · Part 3 · Pointers · about 15 minutes

In lesson 8, a function couldn't change the caller's variable: it only got a copy of the value. Now that you know pointers, there's a way around that. Give the function the variable's address, and it can follow the address back to the original.

main.c
1#include <stdio.h>2 3void add_bonus(int *score) {4    *score = *score + 10;5}6 7int main(void) {8    int score = 80;9    add_bonus(&score);10    printf("In main: %d\n", score);11    return 0;12}
Output
In main: 90

This time main's score really is 90. The parameter is int *score, a pointer, and the call passes &score, an address.

Copies can't swap

Swapping two variables takes three lines and a spare variable. Put those lines in a function and it seems to do nothing:

main.c
1#include <stdio.h>2 3void swap(int a, int b) {4    int tmp = a;5    a = b;6    b = tmp;7}8 9int main(void) {10    int x = 1;11    int y = 2;12    swap(x, y);13    printf("x = %d, y = %d\n", x, y);14    return 0;15}
Output
x = 1, y = 2

Press Step and watch the two stack frames. swap's frame gets its own a and b, copies of 1 and 2. They do get swapped, but only inside swap's box. When it returns, the box is gone, and x and y in main's box never changed.

Passing addresses

Make the parameters pointers, and pass &x and &y. Inside, *a is main's x and *b is main's y:

main.c
1#include <stdio.h>2 3void swap(int *a, int *b) {4    int tmp = *a;5    *a = *b;6    *b = tmp;7}8 9int main(void) {10    int x = 1;11    int y = 2;12    swap(&x, &y);13    printf("x = %d, y = %d\n", x, y);14    return 0;15}
Output
x = 2, y = 1

Press Step again. This time the arrows go from swap's frame across to main's frame: a points at x, b at y. On lines 5 and 6, the values change in main's box. tmp is an ordinary int, so it has no star.

C still passes by value here: swap gets copies of two addresses. But a copy of an address still leads to the same place.

Guess first

What does this print?

1#include <stdio.h>2 3void twice(int *n) {4    *n = *n * 2;5}6 7int main(void) {8    int steps = 3;9    twice(&steps);10    twice(&steps);11    printf("%d\n", steps);12    return 0;13}

Returning two results

return gives back only one value. When a function has two answers, the caller can pass in the addresses of two variables for the function to fill in:

main.c
1#include <stdio.h>2 3void split_time(int total, int *hours, int *minutes) {4    *hours = total / 60;5    *minutes = total % 60;6}7 8int main(void) {9    int h;10    int m;11    split_time(135, &h, &m);12    printf("%d h %d min\n", h, m);13    split_time(50, &h, &m);14    printf("%d h %d min\n", h, m);15    return 0;16}
Output
2 h 15 min
0 h 50 min

h and m have no values to begin with, and that's fine: split_time only writes to them. Using pointer parameters for results is common in C. Name them clearly, so it's obvious which ones are input and which are results.

Here is the smallest and largest of an array, found in one pass:

main.c
1#include <stdio.h>2 3void min_max(int a[], int count, int *low, int *high) {4    *low = a[0];5    *high = a[0];6    for (int i = 1; i < count; i++) {7        if (a[i] < *low) {8            *low = a[i];9        }10        if (a[i] > *high) {11            *high = a[i];12        }13    }14}15 16int main(void) {17    int temps[5] = {18, 23, 15, 27, 21};18    int low;19    int high;20    min_max(temps, 5, &low, &high);21    printf("Low %d, high %d\n", low, high);22    return 0;23}
Output
Low 15, high 27

The array is passed as int a[] with its count beside it. Lesson 17 explains what really gets passed there.

Why scanf needs &

Now the & in scanf("%d", &age) from lesson 3 makes sense. scanf is a function like any other. If you passed it age, it would get a copy of a value, and could never put the number into your variable. So you pass it the address, and it writes through it, just like split_time.

main.c
1#include <stdio.h>2 3int main(void) {4    int age;5    int *p = &age;6    scanf("%d", p);7    printf("Next year you'll be %d\n", age + 1);8    return 0;9}
Output
Next year you'll be 37

p already holds the address of age, so p works just as well as &age. printf needs no &: it only reads the values, so copies are enough.

Forgetting the &

If the parameter is a pointer, you have to pass an address. Here the & is missing:

main.c
1#include <stdio.h>2 3void swap(int *a, int *b) {4    int tmp = *a;5    *a = *b;6    *b = tmp;7}8 9int main(void) {10    int x = 0;11    int y = 7;12    swap(x, y);13    printf("x = %d, y = %d\n", x, y);14    return 0;15}
Output
main.c: In function 'main':
main.c:12:10: warning: passing argument 1 of 'swap' makes pointer from integer without a cast [-Wint-conversion]
   12 |     swap(x, y);
      |          ^
      |          |
      |          int
main.c:3:16: note: expected 'int *' but argument is of type 'int'
    3 | void swap(int *a, int *b) {
      |           ~~~~~^
main.c:12:13: warning: passing argument 2 of 'swap' makes pointer from integer without a cast [-Wint-conversion]
   12 |     swap(x, y);
      |             ^
      |             |
      |             int
main.c:3:24: note: expected 'int *' but argument is of type 'int'
    3 | void swap(int *a, int *b) {
      |                   ~~~~~^
load of null pointer

What happened: Stopped at line 4. This pointer is NULL: nothing can be read or written through it.

makes pointer from integer means: it wanted a pointer (an address) and got an int. This gcc only gives a warning (from gcc 14 on, it's an error), and the program still runs: the value in x is used as an address. x is 0, and address 0 is NULL, so following a on line 4 goes nowhere. Our engine stops there. Press "see how it went wrong". The fix is swap(&x, &y).

The other common mistake is leaving out a star inside the function: a = b; would only copy one address over another in swap's frame, and main would see nothing.

Tip: When should a function take a pointer? If it only needs to read a value, pass the value. If it has to change the caller's variable, or give back more than one result, pass the address.

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it.

Exercise 1

Deposit

Finish deposit so it adds amount to the balance that balance points to. The program should print Balance: 150.

It should print
Balance: 150
exercise1.c
1#include <stdio.h>2 3void deposit(int *balance, int amount) {4 5}6 7int main(void) {8    int balance = 100;9    deposit(&balance, 50);10    printf("Balance: %d\n", balance);11    return 0;12}
Output
Press Run or Ctrl+Enter
Exercise 2

Dollars and cents

Write split_price, which turns a price in cents into dollars and cents through two pointers. split_price(1295, &d, &c) makes d 12 and c 95. The program should print $12 and 95 cents.

It should print
$12 and 95 cents
exercise2.c
1#include <stdio.h>2 3// write split_price here4 5int main(void) {6    int d;7    int c;8    split_price(1295, &d, &c);9    printf("$%d and %d cents\n", d, c);10    return 0;11}
Output
Press Run or Ctrl+Enter
Exercise 3

Smaller first

Write sort2, which takes the addresses of two int variables and swaps them if the first is larger, so the smaller one ends up first. The program should print 3 8 and then 4 9.

It should print
3 8
4 9
exercise3.c
1#include <stdio.h>2 3// write sort2 here4 5int main(void) {6    int a = 8;7    int b = 3;8    sort2(&a, &b);9    printf("%d %d\n", a, b);10    int c = 4;11    int d = 9;12    sort2(&c, &d);13    printf("%d %d\n", c, d);14    return 0;15}
Output
Press Run or Ctrl+Enter

What you learned

  • A function gets copies of its arguments, so changing a parameter doesn't change the caller's variable.
  • Pass an address instead (swap(&x, &y) with int *a parameters), and the function can change the original through *a.
  • Stepping shows it: arrows go from the called function's frame to the variables in the caller's frame.
  • return gives back one value; pointer parameters let a function hand back more results.
  • scanf needs & for the same reason: it has to know where your variable is to put a value into it.
  • Passing an int where a pointer is expected gets a warning (an error from gcc 14 on), and the program goes wrong: remember the &.