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.

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) and void 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 position pos, shifts the others left, updates *n and 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): puts value at pos, 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): merges a and b, both sorted, into res, 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_max calls maximum instead of repeating the loop: two simple passes are worth more than one convoluted one.
  • The condition is double: v[i] < max rules out the maximum (even if it repeats) and v[i] > second keeps 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, like find_pos in the practice.
  • report avoids 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
42YesYes (42 > -1)42
7, 19YesNo42
85No—42
26YesNo42
85No—42
54YesYes54
13YesNo54

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 *n in both functions: the route size lives in main and the functions change it. Inside you write *n (and (*n)--, with brackets); in the call, &n.
  • Validation comes first: pos >= *n when removing (there’s no box *n) and pos > *n when inserting (you can insert right at the end), plus *n >= max so 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. main decides which message to give.
Removing and inserting by shiftingFive rows: the route with 112, 118 and 125 shifting left when it is removed, n becomes 4, 125 shifts right to make room for 120 and n goes back to 5.start, n = 5101104112118125remove pos 2101104118125·n = 4101104118125insert 120 at 3101104118120125n = 5101104118120125←→
To remove you go left to right; to insert, right to left.

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 = k at the end: k has counted every departure copied; the size reaches main through the pointer. With the empty timetable (nb = 0) the first while never starts and the whole of 201 is copied.
Merging two sorted timetablesThe five departures of route 201 in violet and the seven of 202 in coral are merged into an array of twelve, sorted, keeping the colour of where they came from.Route 2016107108109101010Route 202640700820950110012101300Together6106407007108108209109501010110012101300at each step the smaller of the two heads goes in (a[i] or b[j])when 201 runs out, the rest of 202 is copied
The colour tells you which route each departure comes from: no sorting at the end is needed.

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_max without calling maximum, keeping two variables (first and second) that are updated in the same loop. Watch out for the case where a new value larger than first appears: the old first becomes second.
  • Remove by value. Write int remove_value(int v[], int *n, int code), which finds the stop by its code (reusing a search) and calls remove_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 *n ends at the right number.
  • Merge without repeats. Change merge so that, if both routes leave at the same time, that time appears only once in the combined timetable. Hint: when a[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:

  1. On entering the function, v points to the stops array and n points to main‘s n variable: two arrows.
  2. removed stores 112 before anyone overwrites it.
  3. In the for, boxes 2 and 3 of stops get 118 and 125: the change shows up directly in main‘s frame.
  4. On (*n)--, main‘s n box goes from 5 to 4. If you’d written *n--, you’d see the arrow move and the 5 stay put.
  5. Back in main, q gets 112 and the program prints removed 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…CodeResult
A prototypeint ticket_price(int zones);can be called before it’s defined
A function that returnsint f(int x) { return x * 2; }f(3) is 6
A function with no return valuevoid separator(int length)does something and returns nothing
No parametersint first_departure(void)called as first_departure()
Leave a void earlyreturn;ends with no value
The correct mainint main(void) { …; return 0; }exit code 0

Value, reference and arrays

I want…CodeResult
It only reads some datavoid f(int x) and f(credit)works on a copy
It changes some datavoid f(int *x) and f(&credit)inside, *x = …
Increment what’s pointed to(*n)++;with brackets
Return two resultsint f(…, int *pos)one via return, the other via *pos
Pass an arrayvoid f(int v[], int n) and f(v, n)can modify it
A read-only arrayvoid f(const int v[], int n)gcc blocks writes
An array’s sizesizeof v / sizeof v[0] in mainnever inside the function
Change one element through a pointerswap(&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

SymptomLikely cause
implicit declaration of functionmissing prototype or #include
conflicting types forthe prototype and the definition don’t match
main‘s variable doesn’t changepassed by value: missing * in the parameter and & in the call
A size that doesn’t updaten passed by value, or *n-- instead of (*n)--
sizeof on array function parametersizeof of an array inside the function
control reaches end of non-void functiona path with no return
An absurd number as the resultthe 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

  1. Are all the prototypes at the top and do they match their definitions?
  2. Do the variables a function has to change arrive as pointers, with & in the call?
  3. Does every function that receives an array also receive its size?
  4. 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.

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 *