Loops in C — theory with for, while and do…while counting the bus stops (and the ; that only stopped at stop 6)

I was coming back from uni on the number 12 bus and, to kill time, I wanted to write the silliest program in the world: print the five stops on the route, one per line. A textbook for. I compiled it on my laptop, ran it and got this:

#include <stdio.h>

int main(void) {
    int stop;

    printf("Stops on line 12:\n");
    for (stop = 1; stop <= 5; stop++);
    {
        printf("Stop %d\n", stop);
    }
    printf("End of route\n");
    return 0;
}

Output:

Stops on line 12:
Stop 6
End of route

One single stop, and it was stop 6, which does not even exist. Five minutes staring at the code until I saw it: a ; at the end of the for line. That semicolon is an empty body. The loop did its five laps doing nothing, stop ended up as 6 and the block in braces ran once, already outside the loop. gcc had warned me, by the way, and I did not read it:

hook.c: In function 'main':
hook.c:7: warning: this 'for' clause does not guard... [-Wmisleading-indentation]
hook.c:8: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'for'

In this article we cover the three loops in C (for, while and do…while), the order in which each part runs, what the variable is worth when the loop ends, break, continue, nested loops and the mistakes that come up again and again in IC2: the ; from the story, the extra lap caused by <=, the scanf that leaves a loop spinning forever and the break that does not leave where you thought. We come from conditionals in C, where the bus ticket machine could only serve one rider; here it learns to serve all of them. And if you want the complete programs, they are in the loops in C practice.

The bus lines, stops and fares in this topic are made up for the example. Every program has been compiled with gcc -Wall -std=c11 (gcc 14) and the outputs are the real ones. If you have not set up your environment yet, follow the gedit and gcc on Fedora guide, or try them straight away in the site’s C playground.

The for loop in C

In Python, for x in list walks through a sequence. In C there are no sequences to walk through: for is a counter loop with three parts separated by ; inside the parentheses:

for (initialisation; condition; update) {
    // body: what gets repeated
}
  • Initialisation: runs only once, before starting.
  • Condition: checked before every lap. If it is true (non-zero), the body runs; if it is false, the loop ends.
  • Update: runs at the end of every lap, after the body.

Let’s see it on the bus: four stops, and three riders get on at each one.

#include <stdio.h>

int main(void) {
    int stop;
    int riders = 0;                 // on board when leaving the first stop

    for (stop = 1; stop <= 4; stop++) {
        riders += 3;                // 3 get on at every stop
        printf("Stop %d: %2d riders on board\n", stop, riders);
    }
    // The for has finished: stop kept the value that made the condition false
    printf("On leaving the for, stop is %d\n", stop);
    return 0;
}

Output:

Stop 1:  3 riders on board
Stop 2:  6 riders on board
Stop 3:  9 riders on board
Stop 4: 12 riders on board
On leaving the for, stop is 5

The last line is the one that surprises people the first time. Why is stop 5 if there are only four stops? Because the for does not end when the fourth lap finishes, but when the condition is false. After lap 4, the update sets stop to 5, the condition asks 5 <= 4, the answer is No and the loop ends. The drawing shows it step by step:

The order in which a for runsThe line for (stop = 1; stop <= 4; stop++) with its three parts in colour and below how it runs: ① stop = 1 only once; four laps with ② the check (Yes), the body (3, 6, 9 and 12 on board) and ③ stop++; and one last check 5 <= 4 that gives No and leaves stop at 5.for (stop = 1; stop <= 4; stop++)① once at the start · ② before every lap · ③ at the end of every lap① stop = 1(never repeated)1② 1 <= 4: Yesbody: 3 on board③ stop = 22② 2 <= 4: Yesbody: 6 on board③ stop = 33② 3 <= 4: Yesbody: 9 on board③ stop = 44② 4 <= 4: Yesbody: 12 on board③ stop = 5end② 5 <= 4: Noleaves the for with stop = 5 (the value that made the condition false)
The condition is checked five times for four laps: the last one is the one that says No. That is why stop is 5 on the way out.

Checking it by hand: the condition is evaluated with 1, 2, 3, 4 (four Yeses) and with 5 (the No). Five checks for four laps, always one more. And the riders: 3 + 3 + 3 + 3 = 12.

Lapstop at the startstop <= 4?riders after the bodystop after stop++
11Yes32
22Yes63
33Yes94
44Yes125
—5No: leaves12—

Python range() equivalents in C

If you come from Python (from the Python loops topic), this table saves you thinking. Each range translates by changing the start, the condition and the step:

#include <stdio.h>

int main(void) {
    int i;

    printf("range(5)         -> ");
    for (i = 0; i < 5; i++) printf("%d ", i);           // 0 1 2 3 4
    printf("\nrange(1, 6)      -> ");
    for (i = 1; i <= 5; i++) printf("%d ", i);          // 1 2 3 4 5
    printf("\nrange(0, 10, 2)  -> ");
    for (i = 0; i < 10; i += 2) printf("%d ", i);       // in steps of 2
    printf("\nrange(5, 0, -1)  -> ");
    for (i = 5; i > 0; i--) printf("%d ", i);           // backwards
    printf("\n");
    return 0;
}

Output:

range(5)         -> 0 1 2 3 4 
range(1, 6)      -> 1 2 3 4 5 
range(0, 10, 2)  -> 0 2 4 6 8 
range(5, 0, -1)  -> 5 4 3 2 1 
PythonCWatch out for
range(5)for (i = 0; i < 5; i++)The end is not included: <
range(1, 6)for (i = 1; i <= 5; i++)To include 5, <=
range(0, 10, 2)for (i = 0; i < 10; i += 2)The step goes in the update
range(5, 0, -1)for (i = 5; i > 0; i--)Backwards: > and i--

If the loop has to go backwards, the comparison (> or >=) and the update (i--) change together. If you only change one, it either never goes in or never comes out.

Variable scope in the for loop

In old C (C89) the loop variable had to be declared before the for. Since C99 it can be declared inside, and with gcc -std=c11 (what we use throughout the course) it simply works:

int i;                        // outside: still alive after the for
for (i = 0; i < 3; i++) { }

for (int j = 0; j < 3; j++) { }   // inside: only exists in the for

The difference is not about style: it changes where the variable exists. If you declare it inside and try to use it afterwards, it does not compile:

#include <stdio.h>

int main(void) {
    for (int i = 0; i < 3; i++) {    // i only exists inside the for
        printf("lap %d\n", i);
    }
    printf("i on exit: %d\n", i);    // i no longer exists here
    return 0;
}
scope.c: In function 'main':
scope.c:7: error: 'i' undeclared (first use in this function)
scope.c:7: note: each undeclared identifier is reported only once for each function it appears in

So which one should you use? If after the loop you need to know where it stopped (like in the IC2 lab sessions, which move a pointer back with asientos = asientos - i), declare it outside. Otherwise, inside: that way nobody uses it by mistake.

What the variable is worth on exit: < vs <=

This is one of the mistakes that costs the most marks in IC2, because the program compiles and almost works. Look at three similar loops and how many laps each one does:

#include <stdio.h>

int main(void) {
    int i;
    int laps;

    laps = 0;
    for (i = 0; i < 5; i++) laps++;
    printf("i = 0; i <  5 : %d laps, i ends at %d\n", laps, i);

    laps = 0;
    for (i = 0; i <= 5; i++) laps++;                 // one lap too many
    printf("i = 0; i <= 5 : %d laps, i ends at %d\n", laps, i);

    laps = 0;
    for (i = 1; i <= 5; i++) laps++;
    printf("i = 1; i <= 5 : %d laps, i ends at %d\n", laps, i);
    return 0;
}

Output:

i = 0; i <  5 : 5 laps, i ends at 5
i = 0; i <= 5 : 6 laps, i ends at 6
i = 1; i <= 5 : 5 laps, i ends at 6

With i = 0; i < 5 there are five laps (0, 1, 2, 3 and 4). With i = 0; i <= 5, six: 5 also goes in. And with i = 1; i <= 5 it is five again, but i ends at 6, not 5. The rule for counting laps without getting it wrong: with <, it is end − start (5 − 0 = 5); with <=, one more (5 − 1 + 1 = 5; 5 − 0 + 1 = 6). And on exit, the variable is always the first value that made the condition false.

Writing <= when you meant < (or the other way round) gives one lap too many or too few, and the compiler does not warn you. Count the laps with the rule above before compiling, especially when the loop walks through an array: in the pointers topic you will see that the extra lap reads memory that is not yours.

The increment operators — i++, ++i, i += n

The update is almost always i++, but it is worth knowing exactly what it does, because ++ also shows up outside for loops:

#include <stdio.h>

int main(void) {
    int a = 5, b = 5;
    int x = a++;       // x gets 5 and THEN a becomes 6
    int y = ++b;       // b becomes 6 and THEN y gets 6
    int k;

    printf("x = %d, a = %d\n", x, a);
    printf("y = %d, b = %d\n", y, b);

    // In a for update, i++ and ++i are the same: the value is not used
    for (k = 0; k < 10; k += 3) {
        printf("%d ", k);
    }
    printf("\n");
    return 0;
}

Output:

x = 5, a = 6
y = 6, b = 6
0 3 6 9 

a++ (post-increment) hands over the old value and then adds: x gets 5 and a ends at 6. ++b (pre-increment) adds first and hands over the new value: y gets 6. In a for update the value handed over is not used, so i++ and ++i do the same. For steps other than 1, i += 3 (0, 3, 6, 9) or i -= 2.

Practical for examples: the accumulator pattern

Three uses you will write a thousand times: repeat something a fixed number of times, accumulate and count backwards.

#include <stdio.h>

int main(void) {
    int i;
    int sum = 0;

    // 7 times table: fixed number of laps, a textbook for
    printf("7 times table:\n");
    for (i = 1; i <= 10; i++) {
        printf("7 x %2d = %2d\n", i, 7 * i);
    }

    // Sum of 1 to 100: the accumulator starts at 0 BEFORE the loop
    for (i = 1; i <= 100; i++) {
        sum += i;
    }
    printf("\nSum 1-100: %d\n", sum);

    // Countdown: start at the top and subtract
    printf("\nCountdown: ");
    for (i = 10; i >= 1; i--) {
        printf("%d ", i);
    }
    printf("Go!\n");
    return 0;
}

Output:

7 times table:
7 x  1 =  7
7 x  2 = 14
7 x  3 = 21
7 x  4 = 28
7 x  5 = 35
7 x  6 = 42
7 x  7 = 49
7 x  8 = 56
7 x  9 = 63
7 x 10 = 70

Sum 1-100: 5050

Countdown: 10 9 8 7 6 5 4 3 2 1 Go!

The sum from 1 to 100 is 5050, which you can check with Gauss’s formula: 100 · 101 / 2 = 5050. Notice that sum = 0 goes before the loop: if you put it inside, it would reset on every lap and you would end up with 100. And in the table, %2d reserves two spaces so that 7 and 70 line up.

The while loop in C

while is the simplest one: it checks the condition before every lap and repeats while it is true. It has no initialisation or update of its own; you write those yourself.

while (condition) {
    // body
    // something here has to end up changing the condition
}

Compared with Python, only the shape changes: compulsory parentheses and braces instead of indentation. Its place is when you do not know how many laps there will be. For example, the driver types the age of every rider who gets on until they type 0:

#include <stdio.h>

int main(void) {
    int age;
    int sum = 0;
    int count = 0;

    printf("Riders' ages (0 to finish):\n");
    // Read in the condition itself: if scanf reads no number or 0 arrives, it stops
    while (scanf("%d", &age) == 1 && age != 0) {
        sum += age;
        count++;
    }

    if (count > 0) {
        printf("Riders: %d\n", count);
        printf("Average age: %.2f\n", (double)sum / count);   // without (double), integer division
    } else {
        printf("Nobody got on\n");
    }
    return 0;
}

With 22, 67, 15 and 0 as input:

Riders' ages (0 to finish):
22
67
15
0
Riders: 3
Average age: 34.67

The condition does two things at once: scanf("%d", &age) == 1 reads a number and checks it managed to (scanf returns how many items it read), and age != 0 checks whether it is the sentinel. Since && short-circuits, if scanf fails age is not even looked at. The sums: 22 + 67 + 15 = 104 and 104 / 3 = 34.67. The (double) is essential: without it, 104 / 3 is integer division and would give 34.

The special value that marks the end of the data (here, 0) is called a sentinel. Pick one that cannot be real data: an age of 0 makes no sense, so it works.

When to use for, while or do…while

Any for can be written as a while and vice versa. These two do exactly the same:

for (i = 0; i < 5; i++) {        i = 0;
    printf("%d ", i);            while (i < 5) {
}                                    printf("%d ", i);
                                     i++;
                                 }

So why are there two? Readability: for puts everything that controls the loop on one line, and while makes it clear that you do not know when you will stop.

SituationLoopWhy
You know how many laps: 10 stops, 30 students, from 1 to nforStart, end and step on one single line
Repeat until a special value arrives (0, a letter)whileYou do not know how many times
Ask for a value and repeat while it is wrongdo…whileIt has to be asked at least once
Repeat until something happens (guessing, filling the bus)while with a flag or while (1) with breakThe way out is inside the body

The do…while loop — exclusive to C (and Java)

do…while runs the body first and checks the condition afterwards. That is why it always does at least one lap:

do {
    // body: runs at least once
} while (condition);   // here the ; is compulsory

The difference is very clear with a condition that is already false from the start:

#include <stdio.h>

int main(void) {
    int x = 10;

    printf("while:\n");
    while (x < 5) {                       // checked BEFORE: 10 < 5 is false
        printf("  inside the while\n");
    }

    printf("do...while:\n");
    do {
        printf("  inside the do...while\n");
    } while (x < 5);                      // checked AFTER: it has already gone in once
    return 0;
}

Output:

while:
do...while:
  inside the do...while

while checks 10 < 5, sees it is false and never goes in. do…while goes in, prints and then asks. The drawing puts the three loops side by side: notice where the condition diamond is in each one.

Where each loop checks its conditionThree flowcharts. for: i = 0, the condition diamond, the body, i++ and back to the diamond; No leaves. while: diamond, body and back. do…while: the body first and then the diamond, which goes back to the body on Yes. The first two can do 0 laps; do…while does at least one.fori = 0cond?Yesbodyi++No0 laps if the conditionis already falsewhilecond?YesbodyNo0 laps if the conditionis already falsedo…whilebodycond?YesNoat least 1 lap:the condition is at the end
for and while ask before going in; do…while asks on the way out. That is the whole difference, and why it is so good at validating input.

If you want to see the three loops drawn with diamonds and arrows, the way the exam asks for them, there is a whole topic on flowcharts in C.

Is there a do while in Python? No. In Python it is imitated with while True: and a break at the end of the body. In C you can write it like that too (while (1) with break), but do…while says it more clearly.

The perfect use for do…while: validating input

To ask for a value you have to ask at least once, and repeat while it is wrong. That is exactly the shape of do…while:

#include <stdio.h>

int main(void) {
    float grade;

    do {
        printf("Enter a grade (0-10): ");
        scanf("%f", &grade);
        if (grade < 0 || grade > 10) {
            printf("Error: the grade must be between 0 and 10\n");
        }
    } while (grade < 0 || grade > 10);      // repeats WHILE the input is bad

    printf("Valid grade: %.2f\n", grade);
    return 0;
}

With -5, 15 and 7.5 as input:

Enter a grade (0-10): -5
Error: the grade must be between 0 and 10
Enter a grade (0-10): 15
Error: the grade must be between 0 and 10
Enter a grade (0-10): 7.5
Valid grade: 7.50

The condition of the final while is the bad-input condition: grade < 0 || grade > 10. While it is true, it repeats. Without do…while you would have to write the printf and the scanf twice, once before the loop and once inside.

Now, the IC2 trap. What happens if instead of a number someone types the word seven? This:

Enter a grade (0-10): seven
Error: the grade must be between 0 and 10
Enter a grade (0-10): Error: the grade must be between 0 and 10
Enter a grade (0-10): Error: the grade must be between 0 and 10
Enter a grade (0-10): Error: the grade must be between 0 and 10
[emergency brake: 4 laps]

I added a 4-lap emergency brake (the laps lines) so the article is not infinite; without it, the program prints the error non-stop until you press Ctrl+C. Why does it not wait for you to type something else? Because scanf("%f") cannot read seven as a number, returns 0 and leaves the letters in the input. On the next lap it finds the same letters, fails again, and so on forever. Besides, grade does not change, so the condition is still true.

Using scanf inside a loop without checking what it returns. With a number it works; with a letter, infinite loop. Always check scanf(...) == 1 (or the number of items you expect).

The safe version checks what scanf returns and, if it fails, throws away the rest of the line:

#include <stdio.h>

int main(void) {
    float grade = -1;
    int read_count;                           // how many items scanf has read
    int c;

    do {
        printf("Enter a grade (0-10): ");
        read_count = scanf("%f", &grade);
        if (read_count != 1) {
            printf("Error: that is not a number\n");
            while ((c = getchar()) != '\n' && c != EOF);   // empties the line; the ; is the body
            grade = -1;                                    // forces another lap
        } else if (grade < 0 || grade > 10) {
            printf("Error: the grade must be between 0 and 10\n");
        }
    } while (grade < 0 || grade > 10);

    printf("Valid grade: %.2f\n", grade);
    return 0;
}

With seven, 12 and 7.5:

Enter a grade (0-10): seven
Error: that is not a number
Enter a grade (0-10): 12
Error: the grade must be between 0 and 10
Enter a grade (0-10): 7.5
Valid grade: 7.50

The key line is while ((c = getchar()) != '\n' && c != EOF);. It reads character by character up to the Enter (or the end of the input) and does nothing else: its body is the ;. Yes, the same ; from the story, but this time on purpose: here all the work is done by the condition. When you write one like this, leave a comment so nobody thinks it is a mistake.

break and continue in C

  • break ends the loop immediately and jumps to the first statement after it.
  • continue ends only the current lap and jumps to the next one. In a for, “the next one” starts with the update (i++) and then the condition.

Line 12 goes through stops 1 to 10, but stop 4 has roadworks and the shift ends at 8:

#include <stdio.h>

int main(void) {
    int stop;

    printf("Line 12, stops 1 to 10:\n");
    for (stop = 1; stop <= 10; stop++) {
        if (stop == 4) {
            printf("  %d: roadworks, no stop\n", stop);
            continue;                     // jumps to stop++ and goes on with 5
        }
        if (stop == 8) {
            printf("  %d: end of shift\n", stop);
            break;                        // leaves the for: 9 and 10 are never visited
        }
        printf("  %d: opens the doors\n", stop);
    }
    printf("Last stop visited: %d\n", stop);
    return 0;
}

Output:

Line 12, stops 1 to 10:
  1: opens the doors
  2: opens the doors
  3: opens the doors
  4: roadworks, no stop
  5: opens the doors
  6: opens the doors
  7: opens the doors
  8: end of shift
Last stop visited: 8

At 4, continue skips the “opens the doors” printf and the lap carries on with stop++, so 5 arrives normally. At 8, break leaves the for: 9 and 10 are never visited and stop stays at 8 (not 11, which is what would have happened if the loop had ended through the condition).

Now the trap: the same program with while. A while has no automatic update: you write stop++ yourself at the end of the body… and continue skips it.

#include <stdio.h>

int main(void) {
    int stop = 1;
    int laps = 0;

    while (stop <= 10) {
        if (++laps > 8) {              // emergency brake (only for the article)
            printf("[emergency brake: 8 laps]\n");
            break;
        }
        if (stop == 4) {
            printf("  %d: roadworks, no stop\n", stop);
            continue;                  // ALSO skips the stop++ below
        }
        printf("  %d: opens the doors\n", stop);
        stop++;
    }
    return 0;
}

Output (again with an emergency brake, of 8 laps):

  1: opens the doors
  2: opens the doors
  3: opens the doors
  4: roadworks, no stop
  4: roadworks, no stop
  4: roadworks, no stop
  4: roadworks, no stop
  4: roadworks, no stop
[emergency brake: 8 laps]

The bus gets stuck at 4 forever: every lap does continue before reaching stop++, so stop never gets past 4.

Using continue in a while whose step is at the end of the body. It does not happen in a for (i++ runs anyway), but it does in a while. Fix: step before the continue, or turn the loop into a for.

Nested loops — a loop inside another

A loop inside another: for every lap of the outer one, the inner one does all of its laps. It is what you use for tables, matrices and, in general, everything with rows and columns.

#include <stdio.h>

int main(void) {
    int i, j;

    // The outer for picks the row; the inner one walks through that row's columns
    for (i = 1; i <= 5; i++) {
        for (j = 1; j <= 5; j++) {
            printf("%4d", i * j);         // %4d: each number takes 4 spaces
        }
        printf("\n");                     // end of row: outside the inner for
    }
    return 0;
}

Output:

   1   2   3   4   5
   2   4   6   8  10
   3   6   9  12  15
   4   8  12  16  20
   5  10  15  20  25

Five rows by five columns: the inner printf runs 5 · 5 = 25 times and printf("\n") only 5, because it is outside the inner for. If you put it inside, every number would come out on its own line.

break only leaves the inner loop

Let’s look for the first free seat on the bus (seat 3 in row 2) with two loops and a break:

#include <stdio.h>

int main(void) {
    int row, seat;

    // We look for the first free seat on the bus: seat 3 in row 2
    for (row = 1; row <= 3; row++) {
        for (seat = 1; seat <= 4; seat++) {
            printf("(%d,%d) ", row, seat);
            if (row == 2 && seat == 3) {
                printf("<- free ");
                break;                    // only leaves the seats for
            }
        }
        printf("\n");
    }
    return 0;
}

Output:

(1,1) (1,2) (1,3) (1,4) 
(2,1) (2,2) (2,3) <- free 
(3,1) (3,2) (3,3) (3,4) 

Row 3 is walked through completely! break leaves the seats for, but the rows one carries on as if nothing happened. To stop both, the clean way in C is a flag that both conditions check:

#include <stdio.h>

int main(void) {
    int row, seat;
    int found = 0;                     // flag: 1 when the seat turns up
    int free_row = 0, free_seat = 0;

    for (row = 1; row <= 3 && !found; row++) {
        for (seat = 1; seat <= 4 && !found; seat++) {
            printf("(%d,%d) ", row, seat);
            if (row == 2 && seat == 3) {
                found = 1;             // stops both loops at their next check
                free_row = row;        // saved: row and seat are still going to change
                free_seat = seat;
            }
        }
        printf("\n");
    }
    printf("Free: row %d, seat %d\n", free_row, free_seat);
    printf("On exit: row = %d, seat = %d\n", row, seat);
    return 0;
}

Output:

(1,1) (1,2) (1,3) (1,4) 
(2,1) (2,2) (2,3) 
Free: row 2, seat 3
On exit: row = 3, seat = 4

The two grids, with what is really visited in each version:

break in nested loopsTwo grids of 3 rows by 4 seats. With break only: row 1 fully, row 2 up to seat 3 (the free one, in coral) and row 3 fully are visited. With a flag: row 1 fully and row 2 up to seat 3; row 3 is no longer checked.break onlyWith a flagrow 1(1,1)(1,2)(1,3)(1,4)row 2(2,1)(2,2)(2,3)(2,4)row 3(3,1)(3,2)(3,3)(3,4)row 1(1,1)(1,2)(1,3)(1,4)row 2(2,1)(2,2)(2,3)(2,4)row 3(3,1)(3,2)(3,3)(3,4)break only leaves the seats for:row 3 is still walked throughfound = 1 stops both forsat their next checkvisitedthe free onenot checked
This is the real output of seat.c and seat_flag.c: break only breaks the loop it is written in.

Look at the last line of the output: on exit, row is 3 and seat is 4. When found becomes 1, each for still runs its update before checking the condition (seat++ and then row++). That is why we save free_row and free_seat the moment we find it: it is the same thing as before, the variable on exit is “one more”.

Two variables in one for: the comma

The IC2 lab sessions have something like this: for (pv = v, i = 0; pv <= &v[n-1]; pv++, i++). The comma lets you set up and move two variables at once:

#include <stdio.h>

int main(void) {
    int i, j;

    // Two variables in one for: set up and moved on, separated by commas
    for (i = 0, j = 10; i < j; i++, j -= 2) {
        printf("i = %d, j = %2d\n", i, j);
    }
    printf("On exit: i = %d, j = %d\n", i, j);
    return 0;
}

Output:

i = 0, j = 10
i = 1, j =  8
i = 2, j =  6
i = 3, j =  4
On exit: i = 4, j = 2

i goes up 1 at a time and j goes down 2 at a time until they cross: with i = 4 and j = 2, 4 < 2 is false and the loop ends. Careful: the comma only works in the initialisation and the update. In the condition it does not mean “and”: that is what && is for.

Infinite loops and how to get out

A loop whose condition never becomes false is infinite. Sometimes it is a mistake and sometimes it is exactly what you want, as long as there is a break inside:

#include <stdio.h>

int main(void) {
    int on_board = 0;

    while (1) {                           // 1 is always true: infinite loop on purpose
        on_board += 20;
        printf("20 get on: %d on board\n", on_board);
        if (on_board >= 60) {
            printf("Bus full\n");
            break;                        // the only way out
        }
    }
    return 0;
}

Output:

20 get on: 20 on board
20 get on: 40 on board
20 get on: 60 on board
Bus full

while (1) and for (;;) are the two usual ways of writing it (in a for, an empty condition counts as true). They are useful when the way out is decided inside the body: a menu that ends with option 0, a ticket machine that closes at the end of the day… In the IC2 theory you will also see busy waiting, while (turno != 1) {}: an empty loop on purpose that spins until another process changes turno.

If a program hangs in an infinite loop, press Ctrl+C in the terminal to stop it. And to study what it does without flooding the screen, give it a brake like the ones in this article: a counter and a break when it goes past a few laps.

The most common mistakes in IC2

Mistake 1 — Semicolon after the for

It is the one from the story. ; is an empty statement, and the for takes it as its body. If the next line is also indented as if it belonged to the loop, gcc warns with -Wall:

#include <stdio.h>

int main(void) {
    int stop;

    for (stop = 1; stop <= 5; stop++);
        printf("Stop %d\n", stop);
    return 0;
}
indent.c: In function 'main':
indent.c:6: warning: this 'for' clause does not guard... [-Wmisleading-indentation]
indent.c:7: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'for'

Output:

Stop 6

That warning, -Wmisleading-indentation, is your friend: it says “this for does not guard the line below even though it looks like it”. Do not ignore it. With while (cond); it is even worse: if the condition is true, the program hangs without printing anything.

Mistake 2 — Not updating the variable in a while

In a while you write the update yourself, and if you forget it the condition never changes:

#include <stdio.h>

int main(void) {
    int stop = 1;
    int laps = 0;

    while (stop <= 3) {
        printf("Stop %d\n", stop);
        // stop++ is missing: the condition never changes
        if (++laps == 5) {             // emergency brake (only for the article)
            printf("[emergency brake: 5 laps]\n");
            break;
        }
    }
    return 0;
}

Output (with a brake):

Stop 1
Stop 1
Stop 1
Stop 1
Stop 1
[emergency brake: 5 laps]

Before accepting a while, look in the body for the line that brings the condition closer to being false. If it is not there, it is infinite.

Mistake 3 — Wrong condition in a do…while

To validate “the grade must be between 0 and 10”, the condition to repeat is “it is less than 0 or greater than 10″. With && you ask for both at the same time, and that never happens:

#include <stdio.h>

int main(void) {
    float grade;

    do {
        printf("Enter a grade (0-10): ");
        scanf("%f", &grade);
    } while (grade < 0 && grade > 10);      // no grade meets both at once

    printf("Grade accepted: %.2f\n", grade);
    return 0;
}

With -5:

Enter a grade (0-10): -5
Grade accepted: -5.00

It accepts a -5 without complaining. -5 < 0 is true, but -5 > 10 is false, and true && false is false: the loop ends the first time. Trick to get it right: write the good-input condition first (grade >= 0 && grade <= 10) and negate it: !(grade >= 0 && grade <= 10), which is the same as grade < 0 || grade > 10.

Mistake 4 — One lap too many: < vs <=

We saw it with on_exit.c: i = 0; i <= 5 does six laps, not five. If the loop counts stops, it prints one that does not exist; if it walks through an array of 5 elements, it reads the sixth, which is not yours. Count the laps before compiling and check the last value that goes in.

SymptomLikely cause
The body runs only once and with an odd value (6, 11…); right after for (...)
The program hangs without printing anything; after while (...)
The same line repeats non-stopThe update is missing, or a continue skips it
A scanf error repeats without waiting for you to typescanf failed (letters) and you did not empty the input
It accepts values it should reject&& where ` should be in the do…while` condition
One lap too many or too few< vs <=, or starting at 0 vs 1

Watch it step by step in Python Tutor

Python Tutor (pythontutor.com) runs the code line by line and shows the value of each variable at every step. Choose C as the language and paste this program, which combines a continue and a break:

#include <stdio.h>

int main(void) {
    int stop;
    int riders = 0;

    for (stop = 1; stop <= 6; stop++) {
        if (stop == 3) {
            continue;         // nobody gets on at 3
        }
        if (stop == 5) {
            break;            // 5 is the last one of the shift
        }
        riders += stop;       // as many get on as the stop number
    }
    printf("stop = %d, riders = %d\n", stop, riders);
    return 0;
}

Output:

stop = 5, riders = 7

What to watch step by step:

  1. When the for starts, stop = 1 runs only once: you will see the arrow never goes back to that part.
  2. In the lap with stop = 3, continue jumps straight to stop++: riders does not change (it stays at 3).
  3. With stop = 5, break leaves the loop before adding: riders stays at 1 + 2 + 4 = 7.
  4. At the end, stop is 5, not 7: the loop ended through the break, not through the condition.

Quick summary

LoopWhenMinimum lapsHow it is written
forKnown number of laps0for (i = 0; i < n; i++) { … }
whileUntil something happens, not knowing when0while (cond) { … }
do…whileAsking for and validating input1do { … } while (cond);
breakLeave the loop (only the innermost one)—if (…) break;
continueSkip to the next lap—if (…) continue;
while (1) / for (;;)The way out is decided inside—With a break inside

Three ideas to take away:

  1. The condition is checked once more than there are laps. On exit, the variable is the first value that made it false: stop ends at 5 with stop <= 4.
  2. A ; after for (...) or while (...) is an empty body. It compiles, and if gcc warns you with -Wmisleading-indentation, listen to it.
  3. Every scanf inside a loop gets checked (== 1) and, if it fails, the input gets emptied. Otherwise one letter leaves it spinning forever.

Now it is time to write whole programs: in the loops in C practice the ticket machine serves several riders, we calculate series and the number guessing game finally has several attempts. And in the next topic, pointers in C, loops walk through memory for real.

Next step: practiceLoops in C — practice with the bus ticket machine, number series and a 7-attempt guessing game (and the cent the machine kept overcharging me)

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 *