break, continue and goto

The loop from lesson 14 runs to its condition. Three jump statements change that: break leaves the nearest loop or switch, continue starts the next iteration, and goto jumps to a label anywhere in the same function. The first two are ordinary tools; the third has a narrow, well-known use.

break

break ends the innermost loop or switch immediately. A search is the typical case: once the answer is found, further iterations have nothing to add. Without the break the loop would keep testing numbers after the divisor is known.

#include <stdio.h> int main(void) { int number = 91; int divisor = 0; for (int i = 2; i < number; i++) { if (number % i == 0) { divisor = i; break; } } printf("smallest divisor of %d: %d\n", number, divisor); return 0; }

gcc -Wall -Wextra break.c -o break
./break

smallest divisor of 91: 7

The initial value zero of divisor is the "not found" marker. If the loop ends without a break, the program still has a defined result, which matters for a prime number.

continue

continue skips the rest of the body and runs the update and the next condition test. It keeps the normal path unindented when the loop has a special case. The sum below ignores every even number before it reaches the addition.

#include <stdio.h> int main(void) { int sum = 0; for (int i = 1; i <= 10; i++) { if (i % 2 == 0) { continue; } sum += i; } printf("sum of odd numbers: %d\n", sum); return 0; }

gcc -Wall -Wextra continue.c -o continue
./continue

sum of odd numbers: 25

Inside a switch the meaning changes: continue belongs to the enclosing loop, not to the switch, while break leaves the switch. The program below skips the value 2 entirely and keeps the loop running.

#include <stdio.h> int main(void) { for (int i = 0; i < 5; i++) { switch (i) { case 2: continue; default: printf("%d ", i); break; } } printf("\n"); return 0; }

gcc -Wall -Wextra switchcontinue.c -o switchcontinue
./switchcontinue

0 1 3 4

The trailing space after 4 is in the output because the loop prints a separator after every value, including the last. The newline comes from the statement after the loop.

Leaving nested loops

break leaves only the inner loop, so a search in a grid would need a flag in the outer condition. A forward goto names the exit directly and keeps both loops simple. The label is a statement of its own and lives in the same function.

#include <stdio.h> int main(void) { int found_row = -1; int found_col = -1; for (int row = 0; row < 3; row++) { for (int col = 0; col < 3; col++) { if (row * 3 + col == 5) { found_row = row; found_col = col; goto found; } } } found: printf("found at row %d, column %d\n", found_row, found_col); return 0; }

gcc -Wall -Wextra nestedgoto.c -o nestedgoto
./nestedgoto

found at row 1, column 2

The values are initialised to -1 so that a missing element would still print something meaningful. A label that is never reached is legal, but GCC warns that it is unused, as the last section shows.

goto for cleanup

The other accepted use is a single exit path. When several checks can fail and each failure must release what earlier steps acquired, a goto cleanup keeps the release code in one place instead of repeating it in every branch. The example uses plain variables so no library is needed; the pattern is the same with buffers and files.

#include <stdio.h> int main(void) { int a = 4; int b = 0; int status = 0; int total = 0; if (a <= 0) { status = 1; goto cleanup; } if (b == 0) { status = 2; goto cleanup; } total = a / b; printf("total: %d\n", total); cleanup: printf("cleanup, status %d\n", status); return status; }

gcc -Wall -Wextra cleanup.c -o cleanup
./cleanup; echo "exit: $?"

cleanup, status 2
exit: 2

The division is never reached because b is zero, and the cleanup block still runs. The status becomes the exit code, which is the convention from lesson 3. A jump backward can build a loop, but a real loop statement reads better.

Break outside a loop, unused label

break is only valid inside a loop or a switch. At the top of main there is nothing to leave, and the compiler rejects the statement before anything runs:

#include <stdio.h> int main(void) { break; printf("unreachable\n"); return 0; }

gcc -Wall -Wextra badbreak.c -o badbreak

badbreak.c: In function 'main':
badbreak.c:5:5: error: break statement not within loop or switch
5 | break;
| ^~~~~

A label without a matching goto is the opposite problem: it compiles, runs, and leaves a dead name in the function. With -Wextra, GCC points at the label:

#include <stdio.h> int main(void) { loop: printf("once\n"); return 0; }

gcc -Wall -Wextra unusedlabel.c -o unusedlabel
./unusedlabel

unusedlabel.c: In function 'main':
unusedlabel.c:5:1: warning: label 'loop' defined but not used [-Wunused-label]
5 | loop:
| ^~~~
once

Removing the label, or using it, clears the warning. A label is not a variable and has no scope beyond the function, so two functions may use the same label name without a conflict.

Exercises

  1. Search the numbers from 2 to 100 for the first perfect square that is also even, and stop with break when it is found.
  2. Print the numbers from 1 to 20, skipping multiples of three with continue, and count how many were printed.
  3. Write a search over a three by three grid with a flag in the outer condition instead of goto, and compare the two versions.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. Which construct does break leave?

    Answer

    The nearest loop or switch

    break ends the innermost loop or switch; leaving several nested loops needs a flag or a goto.

  2. What does continue do in a for loop?

    Answer

    Skips the rest of the body and runs the update

    The rest of the body is skipped, then the update and the next condition test run as usual.

  3. What is the accepted use of goto for cleanup in C?

    Answer

    One exit path that releases every acquired resource

    A goto cleanup label keeps the release code in one place instead of repeating it on every failure path.

  4. Where can a label used by goto be placed?

    Answer

    Anywhere in the same function

    A label is visible in the whole function but not outside it, so two functions may reuse the same label name.

  5. What does this program print? #include <stdio.h> int main(void) { int number = 91, divisor = 0; for (int i = 2; i < number; i++) { if (number % i == 0) { divisor = i; break; } } printf("smallest divisor of %d: %d\n", number, divisor); return 0; }

    Answer

    smallest divisor of 91: 7

    The loop stops at the first divisor, and 91 is 7 times 13, so 7 is found first.

  6. What does this program print? #include <stdio.h> int main(void) { int sum = 0; for (int i = 1; i <= 10; i++) { if (i % 2 == 0) { continue; } sum += i; } printf("sum of odd numbers: %d\n", sum); return 0; }

    Answer

    sum of odd numbers: 25

    The even values are skipped, and 1 plus 3 plus 5 plus 7 plus 9 is 25.

  7. What does this program print? #include <stdio.h> int main(void) { for (int i = 0; i < 5; i++) { switch (i) { case 2: continue; default: printf("%d ", i); break; } } printf("\n"); return 0; }

    Answer

    0 1 3 4

    The value 2 hits continue, which skips the print, while the other values print before their break.

  8. What does this program print? #include <stdio.h> int main(void) { int found_row = -1, found_col = -1; for (int row = 0; row < 3; row++) { for (int col = 0; col < 3; col++) { if (row * 3 + col == 5) { found_row = row; found_col = col; goto found; } } } found: printf("found at row %d, column %d\n", found_row, found_col); return 0; }

    Answer

    found at row 1, column 2

    The grid is filled row by row, and row * 3 + col equals 5 at row 1, column 2.

  9. This program is built with gcc -std=c17 -Wall -Wextra badbreak.c -o badbreak. What does GCC report? #include <stdio.h> int main(void) { break; printf("unreachable\n"); return 0; }

    Answer

    error: break statement not within loop or switch

    There is no loop or switch to leave, so the compiler rejects the statement.

  10. This program is built with gcc -std=c17 -Wall -Wextra unusedlabel.c -o unusedlabel. What does GCC warn about? #include <stdio.h> int main(void) { loop: printf("once\n"); return 0; }

    Answer

    label 'loop' defined but not used [-Wunused-label]

    The label is never the target of a goto, so -Wextra points at it.

The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev

Banner image