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.
Table of Contents
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 tovalue(an early run counts as 0 minutes late).void multiply_even(int *v, int n, int factor): multiplies the even delays byfactor.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):cis 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 = ∑ /* 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.
#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
foritself. 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 + nis enough. - The check starts again from the beginning (
o = timetable, d = backup): after the copy, both pointers had been left past the end. o - timetablegives the box with no separate counter. We turn it into anintto 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&.palready is the address of the right box; with&pyou’d pass the address of the pointer: gcc warns about the format and the number would be written over the pointer itself.- The
whilearoundscanfrepeats the same stop until it reads a number, andwhile (getchar() != '\n');empties the bad line. Without it, “lots” would stay in the input andscanfwould fail again and again. *p % 2 == 0works 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 theelse.- The mean as a
double: withint, 33 / 8 would give 4 and some “above the mean” would come out wrong.
Trace of the second pass (mean 4.00):
| Box | *p | Sign | Even? | > mean? |
|---|---|---|---|---|
| 0 | 15 | adds | No | Yes |
| 1 | −3 | takes away | No | No |
| 2 | 8 | adds | Yes | Yes |
| 3 | −12 | takes away | Yes | No |
| 4 | 0 | same | Yes | No |
| 5 | 7 | adds | No | Yes |
| 6 | −5 | takes away | No | No |
| 7 | 22 | adds | Yes | Yes |
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 throughv, 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
valuewere 2, they would end up as 4). normalisemakes 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.showreceives the title asconst char *: a string is the address of its first letter, andconstpromises 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.
equalleaves as soon as it finds a difference withreturn 0: there’s no point comparing further. Outbound and return already differ in the first box (12 versus 5).rotate_rightkeeps the last one before overwriting it and copies from back to front. The loop stops atp > vbecause box 0 isn’t copied: it gets the saved value at the end.
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:
- On entering the function,
vis an arrow to box 0 ofshifts, inmain‘s frame. laststores a 4 before anyone touches the last box.- On each lap,
p‘s arrow moves back one box and the value on the left is copied to the right: you’ll see1 2 3 3,1 2 2 3and1 1 2 3. - When
preaches box 0,p > vis false and the loop ends without reading outside the array. *v = lastputs the 4 at the front andmainprints4 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… | Code | Result |
|---|---|---|
| A pointer to int | int *p; | p will hold addresses of int |
| Make it point to a variable | p = &credit; | p points at credit |
| Declare and point at once | int *p = &credit; | the same, in one line |
| A pointer that doesn’t point yet | int *p = NULL; | fails on the spot if used |
| Two pointers on one line | int *p, *q; | each with its own * |
| Print an address | printf("%p", (void *)p); | 0x7ffc… |
Reading and writing through the pointer
| I want to… | Code | Result |
|---|---|---|
| Read what it points to | x = *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 using | if (p != NULL) | or if (p) |
| Two hops | int **pp = &p; **pp = 690; | pointer to pointer |
Arrays and arithmetic
| I want to… | Code | Result |
|---|---|---|
| Point at the start | p = v; | same as p = &v[0] |
| Element i | *(p + i) or p[i] | same as v[i] |
| Walk | for (p = v; p < v + n; p++) | stops past the end |
| Walk backwards | for (p = v + n; p > v; p--) and *(p - 1) | without leaving through the front |
| Position of a pointer | p - v | elements, not bytes |
| Jump two at a time | p += 2 | boxes 0, 2, 4… |
Strings
| I want to… | Code | Result |
|---|---|---|
| A modifiable string | char s[] = "Teror"; | 6 boxes with '\0' |
| A string literal | const char *s = "Teror"; | read-only |
| Walk it | for (p = s; *p != '\0'; p++) | letter by letter |
| Array of strings | const 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
| Symptom | Likely cause |
|---|---|
Segmentation fault right at the start | uninitialised pointer or dereferenced NULL |
'p' is used uninitialized | you use *p before giving it an address |
The function changes nothing in main | you passed values, not addresses (missing & or *) |
value computed is not used | *p++ instead of (*p)++ |
returns address of local variable | you return a local array from the function |
| A huge, absurd number at the end | the loop with <= read one box too many |
lvalue required as increment operand | v++ on an array’s name |
makes integer from pointer | int* p, q;: q isn’t a pointer |
Before you hand it in, ask yourself
- Does every pointer point to a variable, an array or
NULLbefore you use*? - Do all the loops stop with
<atv + n? - Do the variables a function has to fill in carry
&in the call? - Do you check for
NULLbefore using the result of a search or ofmalloc?
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.
Did this article help you? Here is how you can support SergioLearns →




What is an operating system — theory with your laptop on a study afternoon, a write() call and a CPU that never rests (and the three programs that weren’t running at the same time)