Pointers in C — solved exercises with the bus: copying timetables, counting passengers, adjusting delays and an array library (plus the cheat sheet for the exam)

The afternoon before the IC2 practical exam I spent an hour chasing a Segmentation fault in a four-line program. The culprit was an int *total; that never pointed at anything: it was writing the passenger total to a random address. That’s why these exercises keep insisting on the same thing: every pointer points at something before it’s used.

These are the exercises for the pointers in C theory and practice, with bus route 201. How to get the most out of them: read the statement and the expected output, try to solve it on your Fedora (or in the site’s C playground), open the hint only if you’ve been stuck for a while and compare with the commented solution. Before the solutions there are three “What’s wrong here?” snippets and, at the end, the complete pointers cheat sheet. Every output comes from a real run with gcc 14.

Basic level

Exercise 1: copying the timetable with pointers

Route 201 has six morning departures, stored as hhmm numbers (610 is 6:10). The backup bus has to leave at the same times. Write a program with two arrays of 6 integers, timetable (with the times) and backup (empty), that copies the first into the second using only pointers, no indexes, and then checks the copy box by box.

Expected output:

Timetable:        610 640 710 740 810 840 

Copying with pointers...

Backup:           610 640 710 740 810 840 

Box by box check:
[0] 610 == 610 equal
[1] 640 == 640 equal
[2] 710 == 710 equal
[3] 740 == 740 equal
[4] 810 == 810 equal
[5] 840 == 840 equal
Correct copy: 6/6 boxes equal

Declare two pointers, o (origin) and d (destination), and give them values in the first part of the for before using them: for (o = timetable, d = backup; o < timetable + n; o++, d++). The comma lets you initialise and advance both at once. The box number for the check comes from o - timetable.

Exercise 2: counting how the bus changes at each stop

At each of the 8 stops we write down the change in passengers: those getting on minus those getting off (15 if 15 more get on than off, -12 if 12 more get off). Write a program that reads the 8 changes with scanf and, walking with a pointer, counts how many stops add passengers, how many take them away and how many stay the same, how many changes are even and odd, how many are above the mean and how many passengers are left at the end. If the user types something that isn’t a number, the program says so and asks again for the same stop.

Expected output (with “lots” typed by mistake at stop 4):

Change in passengers at the 8 stops:
Stop 1: 15
Stop 2: -3
Stop 3: 8
Stop 4: lots
That's not a number. Stop 4: -12
Stop 5: 0
Stop 6: 7
Stop 7: -5
Stop 8: 22

Changes: 15 -3 8 -12 0 7 -5 22 

--- Summary ---
More on than off:     4
More off than on:     3
No change:            1
Even / odd:           4 / 4
Mean:                 4.00
Above the mean:       4
Passengers left:      32

scanf("%d", p) already receives an address, so it takes no &. For the mean you need two passes: the first adds up and the second compares each change with sum / n (worked out as a double). If scanf returns anything other than 1, throw away the rest of the line with while (getchar() != '\n'); before asking again, or it will keep reading “lots” forever.

Intermediate level

Exercise 3: adjusting the route’s delays

We have the delays of 10 trips in minutes (negative means the bus came early) and the arrival time in Teror of 5 trips, in minutes after 9:00. Write three functions that modify the arrays through pointers:

  • void replace_negatives(int *v, int n, int value): changes the negatives to value (an early run counts as 0 minutes late).
  • void multiply_even(int *v, int n, int factor): multiplies the even delays by factor.
  • void normalise(int *v, int n): subtracts the minimum from all of them, so the first arrival becomes 0.

Apply the first two to the delays and the third to the arrivals.

Expected output:

Initial delays:       15 -3 8 -12 6 7 -5 22 4 -8 
Early ones to 0:      15 0 8 0 6 7 0 22 4 0 
Even ones times 2:    15 0 16 0 12 7 0 44 8 0 

Arrivals:             23 18 31 20 26 
From the first one:   5 0 13 2 8 

Inside each function, for (int *p = v; p < v + n; p++): the pointer is born pointing at the array. Since you receive the array’s address, every *p = … changes main‘s original, with no return. To normalise you need two passes: one that finds the minimum and another that subtracts it. If you subtracted while searching, the minimum would change halfway.

Final challenge

Exercise 4: the bus array library

Write a small library of array operations, using only pointers:

  • void add(int *a, int *b, int *c, int n): c is the box-by-box sum (outbound plus return passengers at each stop).
  • int dot_product(int *a, int *b, int n): the sum of the box-by-box products. With the outbound passengers and each stop’s fare in cents, it gives the takings.
  • int equal(int *a, int *b, int n): 1 if the arrays match completely, 0 if not.
  • void rotate_right(int *v, int n): the last one goes to the front and the rest move one box along (the drivers’ shift rota).

Expected output:

=== THE BUS ARRAY LIBRARY ===

Outbound:         12 7 9 4 15 
Return:           5 11 6 13 3 
Out + return:     17 18 15 17 18 
Outbound takings: 6565 cents
Out == return?:   No
Out == out?:      Yes

Shifts:           1 2 3 4 5 
Rotated once:     5 1 2 3 4 
Rotated twice:    4 5 1 2 3 

For the sum, three pointers advancing together (pa, pb and pc). In the dot product, *pa * *pb reads “what pa points to times what pb points to”. To rotate, keep the last one first, walk from back to front copying *(p - 1) into *p and put the saved one in *v. If you walk from front to back, the first value gets copied into every box.

What’s wrong here?

Three snippets with one pointer mistake each. Read the code, predict what will happen and then look at the explanation.

Snippet 1: the total that never had a home

#include <stdio.h>

int main(void) {
    int boarding[4] = {12, 7, 9, 4};
    int *total;                  /* the total was meant to go here */
    *total = 0;
    for (int i = 0; i < 4; i++)
        *total += boarding[i];
    printf("Total: %d\n", *total);
    return 0;
}

What does it print? Think before reading on.

gcc warns while compiling:

counter.c: In function 'main':
counter.c:6: warning: 'total' is used uninitialized [-Wuninitialized]
counter.c:5: note: 'total' was declared here

And when you run it:

Segmentation fault (core dumped)

total is an uninitialised pointer: it holds garbage, and *total = 0; writes a zero at that random address. In this run the address didn’t belong to the program and the system stopped it dead; in another one it might have “worked” by overwriting another variable. Fix: the total needs a real variable to live in, and the pointer points to it.

#include <stdio.h>

int main(void) {
    int boarding[4] = {12, 7, 9, 4};
    int sum = 0;                /* first, a real variable */
    int *total = &sum;          /* and the pointer points to it */
    for (int i = 0; i < 4; i++)
        *total += boarding[i];
    printf("Total: %d\n", *total);
    return 0;
}
Total: 32

Snippet 2: the passenger who never got on

#include <stdio.h>

void board_passenger(int *counter) {
    *counter++;                 /* I want to add 1 to the counter */
}

int main(void) {
    int inside = 30;
    board_passenger(&inside);
    board_passenger(&inside);
    printf("Passengers inside: %d\n", inside);
    return 0;
}

Two passengers get on a bus with 30 inside. How many are there at the end?

add.c: In function 'board_passenger':
add.c:4: warning: value computed is not used [-Wunused-value]
Passengers inside: 30

Still 30. ++ has higher precedence than *, so *counter++ means *(counter++): it moves the pointer (the local copy of the address) forward and reads what was there, which is thrown away. That’s why gcc warns that the computed value isn’t used. Fix: brackets so that it dereferences first and increments afterwards.

#include <stdio.h>

void board_passenger(int *counter) {
    (*counter)++;               /* first *, then ++ */
}

int main(void) {
    int inside = 30;
    board_passenger(&inside);
    board_passenger(&inside);
    printf("Passengers inside: %d\n", inside);
    return 0;
}
Passengers inside: 32

Snippet 3: fares with an expiry date

#include <stdio.h>

int *daily_fares(void) {
    int fares[3] = {155, 140, 120};     /* only lives while the function runs */
    return fares;
}

int main(void) {
    int *t = daily_fares();
    printf("First fare: %d\n", t[0]);
    return 0;
}

The function prepares the day’s fares and returns the array. Which fare does main print?

local.c: In function 'daily_fares':
local.c:5: warning: function returns address of local variable [-Wreturn-local-addr]
Segmentation fault (core dumped)

fares is a local variable: it lives in the function’s frame and disappears when the function ends. The address it returns points to memory that belongs to no one any more. gcc spots it (returns address of local variable) and, in this run, the program ended in Segmentation fault when reading t[0] (gcc may even replace that address with NULL once it sees the error). Fix: let the array live in main and have the function fill it through a pointer.

Returning the address of a local variableOn the left, the fares array exists inside the function. On the right, back in main that frame no longer exists and pointer t points to memory that belongs to no one.1. Inside daily_faresfares[3]155140120return fares; → its address2. Back in mainframe gonethe memory gets reusedint *tt[0] = ?
When the function ends its local variables disappear; the address it returned is no good any more.
#include <stdio.h>

/* main lends its array and the function fills it */
void daily_fares(int *fares) {
    fares[0] = 155;
    fares[1] = 140;
    fares[2] = 120;
}

int main(void) {
    int t[3];                             /* lives in main: still there afterwards */
    daily_fares(t);
    printf("First fare: %d\n", t[0]);
    return 0;
}
First fare: 155

Commented solutions

Solution to exercise 1

#include <stdio.h>

int main(void) {
    int timetable[6] = {610, 640, 710, 740, 810, 840};   /* departures of route 201 (hhmm) */
    int backup[6];                                       /* empty: the copy goes here */
    int n = 6;
    int *o, *d;                                          /* origin and destination */
    int equales = 0;

    printf("Timetable:        ");
    for (o = timetable; o < timetable + n; o++)
        printf("%d ", *o);
    printf("\n\nCopying with pointers...\n\n");

    /* two pointers moving together; both start pointing at something */
    for (o = timetable, d = backup; o < timetable + n; o++, d++)
        *d = *o;

    printf("Backup:           ");
    for (d = backup; d < backup + n; d++)
        printf("%d ", *d);
    printf("\n\nBox by box check:\n");

    for (o = timetable, d = backup; o < timetable + n; o++, d++) {
        int i = (int)(o - timetable);                    /* position = pointer subtraction */
        if (*o == *d) {
            printf("[%d] %d == %d equal\n", i, *o, *d);
            equales++;
        } else {
            printf("[%d] %d != %d DIFFERENT\n", i, *o, *d);
        }
    }
    printf("Correct copy: %d/%d boxes equal\n", equales, n);
    return 0;
}

The decisions that matter:

  • Both pointers are initialised in the for itself. That way it’s impossible to use them before they point at the array, which is exactly the mistake from the story at the top.
  • A single limit, the origin’s. Since both arrays have the same size and both pointers move together, checking o < timetable + n is enough.
  • The check starts again from the beginning (o = timetable, d = backup): after the copy, both pointers had been left past the end.
  • o - timetable gives the box with no separate counter. We turn it into an int to print it with %d.

Solution to exercise 2

#include <stdio.h>

int main(void) {
    int change[8];       /* at each stop: people getting on minus people getting off */
    int n = 8;
    int *p;
    int positives = 0, negatives = 0, zeros = 0, even = 0, odd = 0, above_mean = 0;
    int sum = 0;

    printf("Change in passengers at the 8 stops:\n");
    for (p = change; p < change + n; p++) {            /* p already points to the array */
        printf("Stop %td: ", p - change + 1);
        while (scanf("%d", p) != 1) {                  /* p already is an address: no & */
            while (getchar() != '\n');                 /* throw away the bad line */
            printf("That's not a number. Stop %td: ", p - change + 1);
        }
    }

    printf("\nChanges: ");
    for (p = change; p < change + n; p++) {            /* first pass: sum */
        printf("%d ", *p);
        sum += *p;
    }
    double mean = (double)sum / n;

    for (p = change; p < change + n; p++) {            /* second pass: count */
        if (*p > 0) positives++;
        else if (*p < 0) negatives++;
        else zeros++;
        if (*p % 2 == 0) even++;                       /* -12 % 2 gives 0: even too */
        else odd++;
        if (*p > mean) above_mean++;
    }

    printf("\n\n--- Summary ---\n");
    printf("More on than off:     %d\n", positives);
    printf("More off than on:     %d\n", negatives);
    printf("No change:            %d\n", zeros);
    printf("Even / odd:           %d / %d\n", even, odd);
    printf("Mean:                 %.2f\n", mean);
    printf("Above the mean:       %d\n", above_mean);
    printf("Passengers left:      %d\n", sum);
    return 0;
}

Sums checked: 15 − 3 + 8 − 12 + 0 + 7 − 5 + 22 = 32 passengers at the end, and 32 / 8 = 4.00 on average. More get on than off at 15, 8, 7 and 22 (4 stops); more get off at −3, −12 and −5 (3), and the 0 stays the same. Even: 8, −12, 0 and 22. Above 4: 15, 8, 7 and 22. The decisions:

  • scanf("%d", p) without &. p already is the address of the right box; with &p you’d pass the address of the pointer: gcc warns about the format and the number would be written over the pointer itself.
  • The while around scanf repeats the same stop until it reads a number, and while (getchar() != '\n'); empties the bad line. Without it, “lots” would stay in the input and scanf would fail again and again.
  • *p % 2 == 0 works with negatives: −12 % 2 gives 0. What wouldn’t work is checking odd numbers with *p % 2 == 1, because −3 % 2 gives −1 in C. That’s why we ask about even and the rest go to the else.
  • The mean as a double: with int, 33 / 8 would give 4 and some “above the mean” would come out wrong.

Trace of the second pass (mean 4.00):

Box*pSignEven?> mean?
015addsNoYes
1−3takes awayNoNo
28addsYesYes
3−12takes awayYesNo
40sameYesNo
57addsNoYes
6−5takes awayNoNo
722addsYesYes

Solution to exercise 3

#include <stdio.h>

/* Early runs (negative) count as 0 minutes late */
void replace_negatives(int *v, int n, int value) {
    for (int *p = v; p < v + n; p++)
        if (*p < 0)
            *p = value;              /* I change the original, not a copy */
}

/* Multiplies the even delays by factor */
void multiply_even(int *v, int n, int factor) {
    for (int *p = v; p < v + n; p++)
        if (*p % 2 == 0)
            *p *= factor;
}

/* Subtracts the minimum from all: the earliest becomes 0 */
void normalise(int *v, int n) {
    int minimum = *v;
    for (int *p = v + 1; p < v + n; p++)       /* first pass: the minimum */
        if (*p < minimum)
            minimum = *p;
    for (int *p = v; p < v + n; p++)           /* second pass: subtract */
        *p -= minimum;
}

void show(const char *title, int *v, int n) {
    printf("%s", title);
    for (int *p = v; p < v + n; p++)
        printf("%d ", *p);
    printf("\n");
}

int main(void) {
    int delays[10] = {15, -3, 8, -12, 6, 7, -5, 22, 4, -8};   /* minutes, negative = early */
    int arrivals[5] = {23, 18, 31, 20, 26};                   /* minute of arrival in Teror */

    show("Initial delays:       ", delays, 10);
    replace_negatives(delays, 10, 0);
    show("Early ones to 0:      ", delays, 10);
    multiply_even(delays, 10, 2);
    show("Even ones times 2:    ", delays, 10);

    printf("\n");
    show("Arrivals:             ", arrivals, 5);
    normalise(arrivals, 5);
    show("From the first one:   ", arrivals, 5);
    return 0;
}
  • None of the functions returns anything (void): they work on the original array through v, which is its address. It’s the same mechanism as the swap, applied to every box.
  • Order matters: first the early runs become 0 and then the even ones are multiplied. The new zeros are even, so they get multiplied too (0 × 2 is still 0, but if the replacement value were 2, they would end up as 4).
  • normalise makes two passes. The earliest arrival is 18; subtracting it, 23 − 18 = 5, 18 − 18 = 0, 31 − 18 = 13, 20 − 18 = 2 and 26 − 18 = 8.
  • show receives the title as const char *: a string is the address of its first letter, and const promises the function won’t modify it.

Solution to exercise 4

#include <stdio.h>

/* c[i] = a[i] + b[i], with three pointers moving together */
void add(int *a, int *b, int *c, int n) {
    for (int *pa = a, *pb = b, *pc = c; pa < a + n; pa++, pb++, pc++)
        *pc = *pa + *pb;
}

/* Sum of products box by box */
int dot_product(int *a, int *b, int n) {
    int total = 0;
    for (int *pa = a, *pb = b; pa < a + n; pa++, pb++)
        total += *pa * *pb;            /* the middle * is the product: (*pa) * (*pb) */
    return total;
}

/* 1 if equal box by box, 0 as soon as one differs */
int equales(int *a, int *b, int n) {
    for (int *pa = a, *pb = b; pa < a + n; pa++, pb++)
        if (*pa != *pb)
            return 0;
    return 1;
}

/* The last one goes to the front and the rest shift one to the right */
void rotate_right(int *v, int n) {
    int last = *(v + n - 1);           /* keep it before overwriting it */
    for (int *p = v + n - 1; p > v; p--) /* from back to front */
        *p = *(p - 1);
    *v = last;
}

void show(const char *title, int *v, int n) {
    printf("%s", title);
    for (int *p = v; p < v + n; p++)
        printf("%d ", *p);
    printf("\n");
}

int main(void) {
    int outbound[5] = {12, 7, 9, 4, 15};       /* boarding at each stop on the way out */
    int back[5] = {5, 11, 6, 13, 3};           /* boarding at the same stop on the way back */
    int fare[5] = {155, 155, 140, 140, 120};   /* cents per passenger at each stop */
    int total[5];
    int n = 5;

    printf("=== THE BUS ARRAY LIBRARY ===\n\n");
    show("Outbound:         ", outbound, n);
    show("Return:           ", back, n);
    add(outbound, back, total, n);
    show("Out + return:     ", total, n);
    printf("Outbound takings: %d cents\n", dot_product(outbound, fare, n));
    printf("Out == return?:   %s\n", equales(outbound, back, n) ? "Yes" : "No");
    printf("Out == out?:      %s\n\n", equales(outbound, outbound, n) ? "Yes" : "No");

    int shifts[5] = {1, 2, 3, 4, 5};           /* order of the drivers */
    show("Shifts:           ", shifts, n);
    rotate_right(shifts, n);
    show("Rotated once:     ", shifts, n);
    rotate_right(shifts, n);
    show("Rotated twice:    ", shifts, n);
    return 0;
}
  • Three pointers declared in the for (int *pa = a, *pb = b, *pc = c): each with its own asterisk, which isn’t shared out in a single declaration.
  • The dot product with the outbound passengers and the fares: 12 × 155 + 7 × 155 + 9 × 140 + 4 × 140 + 15 × 120 = 1860 + 1085 + 1260 + 560 + 1800 = 6565 cents, that is, 65.65 euros taken on the way out.
  • equal leaves as soon as it finds a difference with return 0: there’s no point comparing further. Outbound and return already differ in the first box (12 versus 5).
  • rotate_right keeps the last one before overwriting it and copies from back to front. The loop stops at p > v because box 0 isn’t copied: it gets the saved value at the end.
rotate_right step by stepSix rows: the array 1 2 3 4 5, four copies from back to front and the last one placed at the start: 5 1 2 3 4.start, last = 512345*(v+4) = *(v+3)12344*(v+3) = *(v+2)12334*(v+2) = *(v+1)12234*(v+1) = *(v+0)11234*v = last51234
Copy from back to front; the other way round, the 1 would be repeated in every box.

Watch it step by step in Python Tutor

In Python Tutor, choosing C, you can watch rotate_right move the boxes one by one. Paste this short version:

#include <stdio.h>

void rotate_right(int *v, int n) {
    int last = *(v + n - 1);
    for (int *p = v + n - 1; p > v; p--) {
        *p = *(p - 1);
    }
    *v = last;
}

int main(void) {
    int shifts[4] = {1, 2, 3, 4};
    rotate_right(shifts, 4);
    printf("%d %d %d %d\n", shifts[0], shifts[1], shifts[2], shifts[3]);
    return 0;
}

What to watch step by step:

  1. On entering the function, v is an arrow to box 0 of shifts, in main‘s frame.
  2. last stores a 4 before anyone touches the last box.
  3. On each lap, p‘s arrow moves back one box and the value on the left is copied to the right: you’ll see 1 2 3 3, 1 2 2 3 and 1 1 2 3.
  4. When p reaches box 0, p > v is false and the loop ends without reading outside the array.
  5. *v = last puts the 4 at the front and main prints 4 1 2 3.

Cheat sheet: pointers in C

The essentials in 30 seconds

A pointer holds an address. &x gives you the address of x; *p takes you to the data at the address p holds. p + 1 moves one element of the type pointed to and v[i] is *(v + i). Every pointer points to something valid (or to NULL) before it’s used.

Declaring and pointing

I want to…CodeResult
A pointer to intint *p;p will hold addresses of int
Make it point to a variablep = &credit;p points at credit
Declare and point at onceint *p = &credit;the same, in one line
A pointer that doesn’t point yetint *p = NULL;fails on the spot if used
Two pointers on one lineint *p, *q;each with its own *
Print an addressprintf("%p", (void *)p);0x7ffc…

Reading and writing through the pointer

I want to…CodeResult
Read what it points tox = *p;copies the data
Change what it points to*p = *p - 155;changes the original variable
Increment what it points to(*p)++;with brackets
Check before usingif (p != NULL)or if (p)
Two hopsint **pp = &p; **pp = 690;pointer to pointer

Arrays and arithmetic

I want to…CodeResult
Point at the startp = v;same as p = &v[0]
Element i*(p + i) or p[i]same as v[i]
Walkfor (p = v; p < v + n; p++)stops past the end
Walk backwardsfor (p = v + n; p > v; p--) and *(p - 1)without leaving through the front
Position of a pointerp - velements, not bytes
Jump two at a timep += 2boxes 0, 2, 4…

Strings

I want to…CodeResult
A modifiable stringchar s[] = "Teror";6 boxes with '\0'
A string literalconst char *s = "Teror";read-only
Walk itfor (p = s; *p != '\0'; p++)letter by letter
Array of stringsconst char *stops[] = {"A", "B"};array of addresses

Templates from the exercises

/* Copy with two pointers (exercise 1) */
for (o = origin, d = dest; o < origin + n; o++, d++)
    *d = *o;

/* Function that changes the original array (exercise 3) */
void replace(int *v, int n, int value) {
    for (int *p = v; p < v + n; p++)
        if (*p < 0) *p = value;
}

/* Dot product with two pointers (exercise 4) */
for (int *pa = a, *pb = b; pa < a + n; pa++, pb++)
    total += *pa * *pb;

/* Rotate right (exercise 4) */
int last = *(v + n - 1);
for (int *p = v + n - 1; p > v; p--)
    *p = *(p - 1);
*v = last;

Results with the exercises’ data: copy 6/6 equal; 32 passengers left with a mean of 4.00; arrivals normalised to 5 0 13 2 8; takings 6565 cents; shifts rotated to 5 1 2 3 4.

Symptom and likely cause

SymptomLikely cause
Segmentation fault right at the startuninitialised pointer or dereferenced NULL
'p' is used uninitializedyou use *p before giving it an address
The function changes nothing in mainyou passed values, not addresses (missing & or *)
value computed is not used*p++ instead of (*p)++
returns address of local variableyou return a local array from the function
A huge, absurd number at the endthe loop with <= read one box too many
lvalue required as increment operandv++ on an array’s name
makes integer from pointerint* p, q;: q isn’t a pointer

Before you hand it in, ask yourself

  1. Does every pointer point to a variable, an array or NULL before you use *?
  2. Do all the loops stop with < at v + n?
  3. Do the variables a function has to fill in carry & in the call?
  4. Do you check for NULL before using the result of a search or of malloc?

With pointers mastered, the next step is Functions in C: prototypes, void and the pass by value and by reference you’ve just used in all these exercises.

Keep goingAll C articles

Did this article help you? Here is how you can support SergioLearns →

Sigue aprendiendo

Leave a Reply

Your email address will not be published. Required fields are marked *