Functions in C — solved exercises with the bus: the second worst delay, stops that close and open and timetables that merge (plus the cheat sheet for the exam)
In the IC2 mock exam we did in class, my function to remove a stop from the route worked perfectly… inside the function. In main the route still had five stops, because I had passed the size n by value. Two characters, a * in the parameter and a & in the call, separated the good program from the bad one. These exercises are designed so that those two characters come to you without thinking.
These are the exercises for the functions in C theory and practice, with bus route 201. How to get the most out of them: read the statement and the expected output, write the functions with their prototypes at the top, compile with -Wall (or try it 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 functions cheat sheet. Every output comes from a real run with gcc 14.
Table of Contents
Basic level
Exercise 1: the second worst delay
We want to know the day’s worst delay and also the second worst, which must be a value different from the maximum (if 85 appears twice, the second isn’t 85). Write a program with these functions:
int maximum(int v[], int n): returns the maximum value.int second_max(int v[], int n): returns the largest value strictly less than the maximum, or -1 if there isn’t one (all equal).void show(int v[], int n)andvoid report(int v[], int n), which prints the array, the maximum and the second.
Try it with Monday’s delays {42, 7, 19, 85, 26, 85, 54, 13}, Tuesday’s {10, 10, 10, 10} and Wednesday’s {9, 31}.
Expected output:
Delays: 42 7 19 85 26 85 54 13 Maximum: 85 Second maximum: 54 Delays: 10 10 10 10 Maximum: 10 Second maximum: none (all equal) Delays: 9 31 Maximum: 31 Second maximum: 9
There’s no need to sort. First work out the maximum by calling maximum (reuse the function). Then walk the array looking for the largest value that satisfies v[i] < max. Start that second maximum at -1: since delays are never negative, if it’s still -1 at the end there was no different value.
Intermediate level
Exercise 2: closing and opening stops
Route 201 stores its stop codes in an array of up to 10 boxes, of which n are in use. Because of roadworks, one stop closes and another opens. Write:
int remove_at(int v[], int *n, int pos): removes the stop at positionpos, shifts the others left, updates*nand returns the removed code. If the position isn’t valid, it returns -1 without touching anything.int insert_at(int v[], int *n, int pos, int value, int max): putsvalueatpos, shifts the others right and updates*n. It returns 1 if it could and 0 if the position isn’t valid or there’s no room left.void show(int v[], int n).
With {101, 104, 112, 118, 125}: close position 2, open stop 120 at position 3, try to remove position 9 and try to insert stop 99 with a maximum of 5.
Expected output:
Route (5 stops): 101 104 112 118 125 Removed 112 from position 2 Route (4 stops): 101 104 118 125 Inserted 120 at position 3 Route (5 stops): 101 104 118 120 125 Position 9 not valid: nothing changed No room for 99 Route (5 stops): 101 104 118 120 125
The size changes, so n goes by reference: int *n in the parameter and &n in the call; inside, *n to read it and (*n)-- or (*n)++ to change it. To remove, walk from the position to the right, copying v[i + 1] into v[i]. To insert, from *n down to the position, copying v[i - 1] into v[i]. Inserting at pos == *n is valid: it’s appending at the end.
Final challenge
Exercise 3: merging two sorted timetables
Routes 201 and 202 share a stop at San Telmo. Each has its own sorted departure timetable (hhmm numbers) and we want the combined timetable, also sorted. Write:
void merge(int a[], int na, int b[], int nb, int res[], int *nr): mergesaandb, both sorted, intores, which ends up sorted; it leaves the number of departures in*nr.void sort(int v[], int n): sorts an array (bubble sort is fine).void show(const char *title, int v[], int n).
Merge 201 {610, 710, 810, 910, 1010} with 202 {640, 700, 820, 950, 1100, 1210, 1300}. Then sort route 303, which arrives unsorted {1500, 730, 1215, 905}, and merge it with 201. Finally, merge 201 with an empty timetable.
Expected output:
Route 201: 610 710 810 910 1010 Route 202: 640 700 820 950 1100 1210 1300 Together: 610 640 700 710 810 820 910 950 1010 1100 1210 1300 Departures: 12 Route 303 sorted: 730 905 1215 1500 201 + 303: 610 710 730 810 905 910 1010 1215 1500 201 + none: 610 710 810 910 1010
Don’t join the two arrays and sort afterwards: take advantage of them already being sorted. Use three indexes, i for a, j for b and k for res. While both have departures left, copy the smaller of the two “heads” (a[i] or b[j]) and move its index on. When one runs out, copy whatever is left of the other. Always test the empty-array case (nb = 0).
What’s wrong here?
Three snippets, each with a typical function mistake. Read the code, predict what it will print and then look at the explanation.
Snippet 1: the credit that wouldn’t go down
#include <stdio.h>
void charge(int credit, int price) {
credit = credit - price;
}
int main(void) {
int credit = 1000;
charge(credit, 155);
charge(credit, 155);
printf("Credit after two trips: %d\n", credit);
return 0;
}
Two 1.55 euro trips on a 10 euro pass. What credit does it print?
Credit after two trips: 1000
Still 1000. charge receives a copy of the credit, subtracts the trip and the copy dies when the function returns. gcc doesn’t warn: the program is valid C, it just doesn’t do what you wanted. Fix: the function receives the address and writes through it.
#include <stdio.h>
void charge(int *credit, int price) {
*credit = *credit - price; /* writes into main's variable */
}
int main(void) {
int credit = 1000;
charge(&credit, 155);
charge(&credit, 155);
printf("Credit after two trips: %d\n", credit);
return 0;
}
Credit after two trips: 690
1000 − 155 − 155 = 690, two trips really charged.
Snippet 2: counting the boxes inside the function
#include <stdio.h>
int full_stops(int v[]) {
int n = sizeof(v) / sizeof(v[0]); /* how many boxes does it have? */
int c = 0;
for (int i = 0; i < n; i++)
if (v[i] > 40) c++;
return c;
}
int main(void) {
int passengers[6] = {15, 48, 42, 3, 51, 19};
printf("Stops with more than 40: %d\n", full_stops(passengers));
return 0;
}
There are three stops with more than 40 passengers (48, 42 and 51). How many does the function count?
count.c: In function 'full_stops': count.c:4: warning: 'sizeof' on array function parameter 'v' will return size of 'int *' [-Wsizeof-array-argument] count.c:3: note: declared here
Stops with more than 40: 1
Just one. Inside the function, v is a pointer, so sizeof(v) gives 8 and 8 / 4 is 2 boxes: the function only looks at 15 and 48. gcc warns with -Wsizeof-array-argument. Fix: the size is worked out in main, where passengers really is the array, and passed as a parameter.
#include <stdio.h>
int full_stops(int v[], int n) { /* the size travels separately */
int c = 0;
for (int i = 0; i < n; i++)
if (v[i] > 40) c++;
return c;
}
int main(void) {
int passengers[6] = {15, 48, 42, 3, 51, 19};
int n = sizeof passengers / sizeof passengers[0]; /* fine here: in main it is the array */
printf("Stops with more than 40: %d\n", full_stops(passengers, n));
return 0;
}
Stops with more than 40: 3
Snippet 3: the 3-zone ticket
#include <stdio.h>
int price(int zones) {
if (zones == 1)
return 155;
else if (zones == 2)
return 175;
}
int main(void) {
for (int z = 1; z <= 3; z++)
printf("%d zones: %d cents\n", z, price(z));
return 0;
}
How much does the 3-zone ticket cost?
price.c: In function 'price': price.c:8: warning: control reaches end of non-void function [-Wreturn-type]
1 zones: 155 cents 2 zones: 175 cents 3 zones: 3 cents
3 cents. With 3 zones neither if is true, the function reaches its closing brace with no return and returns whatever was in the return register; in this run, a 3 (probably the value of zones, which had been left in that register). On another computer it’ll be something else. gcc warns with control reaches end of non-void function: never ignore that warning. Fix: make every path end in a return.
#include <stdio.h>
int price(int zones) {
if (zones == 1)
return 155;
else if (zones == 2)
return 175;
return 195; /* 3 zones or more: every path returns */
}
int main(void) {
for (int z = 1; z <= 3; z++)
printf("%d zones: %d cents\n", z, price(z));
return 0;
}
1 zones: 155 cents 2 zones: 175 cents 3 zones: 195 cents
Commented solutions
Solution to exercise 1
#include <stdio.h>
int maximum(int v[], int n);
int second_max(int v[], int n);
void show(int v[], int n);
void report(int v[], int n);
int main(void) {
int monday[8] = {42, 7, 19, 85, 26, 85, 54, 13}; /* delays in minutes */
int tuesday[4] = {10, 10, 10, 10};
int wednesday[2] = {9, 31};
report(monday, 8);
report(tuesday, 4);
report(wednesday, 2);
return 0;
}
int maximum(int v[], int n) {
int m = v[0];
for (int i = 1; i < n; i++)
if (v[i] > m) m = v[i];
return m;
}
/* The largest value STRICTLY less than the maximum, or -1 if none */
int second_max(int v[], int n) {
int max = maximum(v, n); /* first pass, reusing */
int second = -1; /* -1: haven't seen one yet */
for (int i = 0; i < n; i++)
if (v[i] < max && v[i] > second) /* not the maximum and better than the current one */
second = v[i];
return second;
}
void show(int v[], int n) {
for (int i = 0; i < n; i++)
printf("%d ", v[i]);
printf("\n");
}
void report(int v[], int n) {
printf("Delays: ");
show(v, n);
printf("Maximum: %d\n", maximum(v, n));
int s = second_max(v, n);
if (s == -1)
printf("Second maximum: none (all equal)\n\n");
else
printf("Second maximum: %d\n\n", s);
}
The decisions that matter:
second_maxcallsmaximuminstead of repeating the loop: two simple passes are worth more than one convoluted one.- The condition is double:
v[i] < maxrules out the maximum (even if it repeats) andv[i] > secondkeeps the best of the rest. - -1 as “none”: it works because delays are non-negative minutes. If the data could be negative, you’d return the result via a pointer and a 1/0 with
return, likefind_posin the practice. reportavoids repeating the four print lines three times: each day is one call.
Trace for Monday ({42, 7, 19, 85, 26, 85, 54, 13}, maximum 85):
v[i] | < 85? | > second? | second |
|---|---|---|---|
| 42 | Yes | Yes (42 > -1) | 42 |
| 7, 19 | Yes | No | 42 |
| 85 | No | — | 42 |
| 26 | Yes | No | 42 |
| 85 | No | — | 42 |
| 54 | Yes | Yes | 54 |
| 13 | Yes | No | 54 |
Solution to exercise 2
#include <stdio.h>
#define MAX 10
int remove_at(int v[], int *n, int pos);
int insert_at(int v[], int *n, int pos, int value, int max);
void show(int v[], int n);
int main(void) {
int stops[MAX] = {101, 104, 112, 118, 125}; /* codes on the route */
int n = 5;
show(stops, n);
int removed = remove_at(stops, &n, 2); /* stop 112 closes */
printf("Removed %d from position 2\n", removed);
show(stops, n);
if (insert_at(stops, &n, 3, 120, MAX)) /* stop 120 opens */
printf("Inserted 120 at position 3\n");
show(stops, n);
if (remove_at(stops, &n, 9) == -1) /* position that doesn't exist */
printf("Position 9 not valid: nothing changed\n");
if (!insert_at(stops, &n, 0, 99, 5)) /* there are 5 already and the maximum is 5 */
printf("No room for 99\n");
show(stops, n);
return 0;
}
/* Removes v[pos], shifts the rest left and updates *n */
int remove_at(int v[], int *n, int pos) {
if (pos < 0 || pos >= *n)
return -1; /* not valid: don't touch anything */
int removed = v[pos];
for (int i = pos; i < *n - 1; i++) /* from left to right */
v[i] = v[i + 1];
(*n)--;
return removed;
}
/* Puts value at pos, shifts the rest right; 1 if it could, 0 if not */
int insert_at(int v[], int *n, int pos, int value, int max) {
if (pos < 0 || pos > *n || *n >= max) /* pos == *n is fine: append at the end */
return 0;
for (int i = *n; i > pos; i--) /* from right to left */
v[i] = v[i - 1];
v[pos] = value;
(*n)++;
return 1;
}
void show(int v[], int n) {
printf("Route (%d stops): ", n);
for (int i = 0; i < n; i++)
printf("%d ", v[i]);
printf("\n");
}
int *nin both functions: the route size lives inmainand the functions change it. Inside you write*n(and(*n)--, with brackets); in the call,&n.- Validation comes first:
pos >= *nwhen removing (there’s no box*n) andpos > *nwhen inserting (you can insert right at the end), plus*n >= maxso as not to write outside the array. - The walk directions are opposite: removing 112 copies from left to right (118 to box 2, 125 to box 3); inserting 120 copies from right to left (125 from box 3 to box 4) and then writes 120.
- The return values report: the removed code (or -1) and 1/0 when inserting.
maindecides which message to give.
Solution to exercise 3
#include <stdio.h>
#define MAX 20
void merge(int a[], int na, int b[], int nb, int res[], int *nr);
void sort(int v[], int n);
void show(const char *title, int v[], int n);
int main(void) {
int l201[5] = {610, 710, 810, 910, 1010}; /* already sorted (hhmm) */
int l202[7] = {640, 700, 820, 950, 1100, 1210, 1300};
int all[MAX], n;
show("Route 201: ", l201, 5);
show("Route 202: ", l202, 7);
merge(l201, 5, l202, 7, all, &n);
show("Together: ", all, n);
printf("Departures: %d\n\n", n);
int l303[4] = {1500, 730, 1215, 905}; /* unsorted */
sort(l303, 4);
show("Route 303 sorted: ", l303, 4);
merge(l201, 5, l303, 4, all, &n);
show("201 + 303: ", all, n);
int empty[1];
merge(l201, 5, empty, 0, all, &n); /* nb = 0 */
show("201 + none: ", all, n);
return 0;
}
/* Merges two sorted arrays into res, also sorted */
void merge(int a[], int na, int b[], int nb, int res[], int *nr) {
int i = 0, j = 0, k = 0;
while (i < na && j < nb) { /* while both have some left */
if (a[i] <= b[j])
res[k++] = a[i++]; /* the smaller of the two heads */
else
res[k++] = b[j++];
}
while (i < na) res[k++] = a[i++]; /* whatever is left of a */
while (j < nb) res[k++] = b[j++]; /* or whatever is left of b */
*nr = k; /* the size goes out via pointer */
}
void sort(int v[], int n) {
for (int i = 0; i < n - 1; i++)
for (int j = 0; j < n - 1 - i; j++)
if (v[j] > v[j + 1]) {
int t = v[j]; v[j] = v[j + 1]; v[j + 1] = t;
}
}
void show(const char *title, int v[], int n) {
printf("%s", title);
for (int i = 0; i < n; i++)
printf("%d ", v[i]);
printf("\n");
}
- Three indexes that only move forward: each departure is looked at once, so merging 5 + 7 departures takes 12 steps. Sorting at the end would take many more.
<=in the comparison: if both routes left at the same time,a‘s is copied first.<would work too;<=keeps the original order, which matters in other problems.- The two final
whiles: only one of them actually runs, the one for the array that hasn’t run out. With 202, the last three departures (1100, 1210 and 1300) go in there. *nr = kat the end:khas counted every departure copied; the size reachesmainthrough the pointer. With the empty timetable (nb = 0) the firstwhilenever starts and the whole of 201 is copied.
Keep practising
If the three exercises worked out, these variations go over the same ground from another angle. They’re designed for the kind of question that comes up in the practical exam, where you’re asked for a specific function and the call from main:
- Second maximum in a single pass. Rewrite
second_maxwithout callingmaximum, keeping two variables (firstandsecond) that are updated in the same loop. Watch out for the case where a new value larger thanfirstappears: the oldfirstbecomessecond. - Remove by value. Write
int remove_value(int v[], int *n, int code), which finds the stop by its code (reusing a search) and callsremove_at. It returns 1 if it removed it and 0 if it didn’t exist. - Remove every occurrence. With the list of repeated routes from the practice, remove every appearance of 201 and check that
*nends at the right number. - Merge without repeats. Change
mergeso that, if both routes leave at the same time, that time appears only once in the combined timetable. Hint: whena[i] == b[j], copy one and move both indexes on.
In all of them, before compiling, write the prototypes first and mark with an arrow which parameters are outputs: if a function changes something of main‘s, that parameter carries * and the call carries &.
Watch it step by step in Python Tutor
In Python Tutor, choosing C, you can watch remove_at change main‘s array and size at the same time. Paste this short version:
#include <stdio.h>
int remove_at(int v[], int *n, int pos) {
if (pos < 0 || pos >= *n) {
return -1;
}
int removed = v[pos];
for (int i = pos; i < *n - 1; i++) {
v[i] = v[i + 1];
}
(*n)--;
return removed;
}
int main(void) {
int stops[5] = {101, 104, 112, 118, 125};
int n = 5;
int q = remove_at(stops, &n, 2);
printf("removed %d, left: %d\n", q, n);
return 0;
}
What to watch step by step:
- On entering the function,
vpoints to thestopsarray andnpoints tomain‘snvariable: two arrows. removedstores 112 before anyone overwrites it.- In the
for, boxes 2 and 3 ofstopsget 118 and 125: the change shows up directly inmain‘s frame. - On
(*n)--,main‘snbox goes from 5 to 4. If you’d written*n--, you’d see the arrow move and the 5 stay put. - Back in
main,qgets 112 and the program printsremoved 112, left: 4.
Cheat sheet: functions in C
The essentials in 30 seconds
A function has a return type, a name, typed parameters and a body. The prototype (the signature with ;) goes at the top so you can call the function before defining it. void means “returns nothing” or “takes nothing”. C passes arguments by value (copies); to modify a caller’s variable, you pass its address. An array is passed as an address and its size separately.
Declaring, defining and calling
| I want… | Code | Result |
|---|---|---|
| A prototype | int ticket_price(int zones); | can be called before it’s defined |
| A function that returns | int f(int x) { return x * 2; } | f(3) is 6 |
| A function with no return value | void separator(int length) | does something and returns nothing |
| No parameters | int first_departure(void) | called as first_departure() |
Leave a void early | return; | ends with no value |
| The correct main | int main(void) { …; return 0; } | exit code 0 |
Value, reference and arrays
| I want… | Code | Result |
|---|---|---|
| It only reads some data | void f(int x) and f(credit) | works on a copy |
| It changes some data | void f(int *x) and f(&credit) | inside, *x = … |
| Increment what’s pointed to | (*n)++; | with brackets |
| Return two results | int f(…, int *pos) | one via return, the other via *pos |
| Pass an array | void f(int v[], int n) and f(v, n) | can modify it |
| A read-only array | void f(const int v[], int n) | gcc blocks writes |
| An array’s size | sizeof v / sizeof v[0] in main | never inside the function |
| Change one element through a pointer | swap(&v[j], &v[j + 1]) | & of each box |
Templates from the exercises
/* Second maximum different from the maximum (exercise 1) */
int max = maximum(v, n), second = -1;
for (int i = 0; i < n; i++)
if (v[i] < max && v[i] > second) second = v[i];
/* Remove at a position with the size by reference (exercise 2) */
if (pos < 0 || pos >= *n) return -1;
for (int i = pos; i < *n - 1; i++) v[i] = v[i + 1];
(*n)--;
/* Insert at a position (exercise 2) */
for (int i = *n; i > pos; i--) v[i] = v[i - 1];
v[pos] = value;
(*n)++;
/* Merge two sorted arrays (exercise 3) */
while (i < na && j < nb)
res[k++] = (a[i] <= b[j]) ? a[i++] : b[j++];
while (i < na) res[k++] = a[i++];
while (j < nb) res[k++] = b[j++];
*nr = k;
Results with the exercises’ data: Monday’s second worst delay is 54 (maximum 85); the route ends up as 101 104 118 120 125; the combined 201 and 202 timetable has 12 departures, from 610 to 1300.
Symptom and likely cause
| Symptom | Likely cause |
|---|---|
implicit declaration of function | missing prototype or #include |
conflicting types for | the prototype and the definition don’t match |
main‘s variable doesn’t change | passed by value: missing * in the parameter and & in the call |
| A size that doesn’t update | n passed by value, or *n-- instead of (*n)-- |
sizeof on array function parameter | sizeof of an array inside the function |
control reaches end of non-void function | a path with no return |
| An absurd number as the result | the function returned garbage through a path with no return |
return type of 'main' is not 'int' | void main instead of int main(void) |
Before you hand it in, ask yourself
- Are all the prototypes at the top and do they match their definitions?
- Do the variables a function has to change arrive as pointers, with
&in the call? - Does every function that receives an array also receive its size?
- Does every path in functions that return something end in a
return?
With functions mastered, the next topic is Dynamic memory in C: malloc returns an address, and the functions you’ve just written will help you work with arrays that grow while the program runs.
Did this article help you? Here is how you can support SergioLearns →




The hardware that holds up the operating system — theory with my IC2 assignment on the printer, an interrupt that stops everything and a cache that hits (and the infinite loop that didn’t freeze my laptop)