MODULE 7 · LESSON 7.3 · THEORY
Break and Continue in Embedded C: Early Exit, Skipping, Nested Loops and goto Cleanup
What break and continue do in for, while and do-while loops, the continue trap, nested loop trip counts, leaving several loops, goto cleanup, row-major order, MISRA rules on multiple exits and embedded patterns. Keep this in mind whenever you use break and continue in embedded C.
Beginner → Intermediate
Level
What You Will Learn
✓ What break does and which statements it can leave
✓ What continue does in for, while and do-while
✓ The classic continue bug in a while loop
✓ Nested loops: how trip counts multiply and how to read them
✓ Leaving several loops at once: flags, functions and goto
✓ Walking 2D arrays in memory order
✓ What MISRA C says about multiple exits
✓ Searching, skipping and early exit patterns for firmware
Key Terms
breakcontinuegotoNested loopInner loopOuter loopTrip countEarly exitRow-major orderFlag variableCleanup labelSearch loopLoop invariantMultiple exitsMISRA 15.4
1. The break Statement
break immediately ends the innermost enclosing loop (for, while, do-while) or switch. Execution continues with the first statement after that loop. The typical use is an early exit: stop as soon as the answer is known. Many bugs with break and continue in embedded C start here.
break leaves the loop at once
loop condition ?
true
↓
statements before break
↓
if (found) break;
jump out
not found
↓
statements after break
then next pass
break goes directly to the first statement after the loop
break_basic.c
#include <stdio.h>
int main(void)
{
int data[] = { 12, 7, 33, 21, 8, 40 };
int n = sizeof data / sizeof data[0];
int target = 21;
int index = –1;
for (int i = 0; i < n; i++)
{
printf(“checking data[%d] = %d\n”, i, data[i]);
if (data[i] == target)
{
index = i;
break; /* no need to look further */
}
}
if (index >= 0) printf(“found %d at index %d\n”, target, index);
else printf(“%d not found\n”, target);
return 0;
}
Output (verified by compiling and running)
checking data[0] = 12
checking data[1] = 7
checking data[2] = 33
checking data[3] = 21
found 21 at index 3
Without break the loop would examine the last two elements for nothing. With 1000 elements and the target at position 3 the saving is large. Remember that break leaves only one level; it does not affect an outer loop. This is the core rule for break and continue in embedded C.
2. The continue Statement
continue skips the rest of the current iteration and goes on with the next one. What exactly “next” means depends on the loop: Reviewers check break and continue in embedded C closely.
| Loop | continue jumps to | Update runs? |
|---|
for | the update expression, then the condition | Yes |
while | the condition test | No (there is no update part) |
do-while | the condition test at the bottom | No |
continue_basic.c
#include <stdio.h>
int main(void)
{
int adc[] = { 2048, 0, 2051, 4095, 2049, 4095, 2050 }; /* 0 and 4095 are invalid (open/short) */
int n = sizeof adc / sizeof adc[0];
long sum = 0;
int good = 0;
for (int i = 0; i < n; i++)
{
if (adc[i] == 0 || adc[i] == 4095)
{
printf(“sample %d (%d) rejected\n”, i, adc[i]);
continue; /* skip the rest of this pass; the update i++ still runs */
}
sum += adc[i];
good++;
}
printf(“average of %d valid samples = %ld\n”, good, sum / good);
return 0;
}
Output (verified by compiling and running)
sample 1 (0) rejected
sample 3 (4095) rejected
sample 5 (4095) rejected
average of 4 valid samples = 2049
continue keeps the main logic unindented, in the same way a guard clause does for functions (Lesson 6.1): reject the bad case early and carry on with the normal path. Interviewers test break and continue in embedded C on exactly this point.
3. The continue Trap in while Loops
In a for loop the update is in the header, so continue cannot skip it. In a while loop the update is in the body, and a continue placed before it makes the loop spin forever. Master break and continue in embedded C by practising this case.
continue_trap.c
#include <stdio.h>
int main(void)
{
int i = 0;
int passes = 0;
while (i < 6)
{
passes++;
if (passes > 20) { printf(“safety exit: stuck at i=%d\n”, i); break; }
if (i == 3)
continue; /* BUG: i++ below is skipped, i stays 3 forever */
printf(“i=%d “, i);
i++;
}
printf(“\n”);
i = 0;
while (i < 6)
{
if (i == 3) { i++; continue; } /* fix: update before continue */
printf(“i=%d “, i);
i++;
}
printf(“\n”);
return 0;
}
Output (verified by compiling and running)
i=0 i=1 i=2 safety exit: stuck at i=3
i=0 i=1 i=2 i=4 i=5
Rule: In while and do-while loops, make sure every path to a continue has already made progress. The safest fix is to turn the loop into a for loop, whose update cannot be skipped. For do-while loops note that continue jumps to the test at the bottom, which is evaluated with the current state.
4. Nested Loops
A loop inside another loop is a nested loop. For every pass of the outer loop the inner loop runs completely. The total number of times the innermost body runs is the product of the trip counts (when the counts are independent).
nested_basic.c
#include <stdio.h>
int main(void)
{
int count = 0;
for (int row = 1; row <= 3; row++)
{
for (int col = 1; col <= 4; col++)
{
printf(“(%d,%d) “, row, col);
count++;
}
printf(“\n”);
}
printf(“inner body ran %d times (3 x 4)\n”, count);
printf(“multiplication table\n”);
for (int a = 1; a <= 4; a++)
{
for (int b = 1; b <= 5; b++)
printf(“%3d”, a * b);
printf(“\n”);
}
return 0;
}
Output (verified by compiling and running)
(1,1) (1,2) (1,3) (1,4)
(2,1) (2,2) (2,3) (2,4)
(3,1) (3,2) (3,3) (3,4)
inner body ran 12 times (3 x 4)
multiplication table
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
Iteration space of the loops above (3 rows x 4 columns)
| col 1 | col 2 | col 3 | col 4 |
|---|
| row 1 | (1,1) first | (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) last |
The inner (column) variable changes fastest; the outer (row) variable changes slowest.
Dependent inner limits: triangles
When the inner limit depends on the outer counter the total is a sum, not a product. This pattern occurs in sorting and in comparing every pair of items.
nested_triangle.c
#include <stdio.h>
int main(void)
{
int pairs = 0;
int n = 5;
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++) /* every pair once */
pairs++;
printf(“distinct pairs among %d items = %d (n*(n-1)/2 = %d)\n”, n, pairs, n * (n – 1) / 2);
for (int row = 1; row <= 4; row++)
{
for (int k = 0; k < row; k++) putchar(‘*’);
putchar(‘\n’);
}
return 0;
}
Output (verified by compiling and running)
distinct pairs among 5 items = 10 (n*(n-1)/2 = 10)
*
**
***
****
5. break in Nested Loops: Leaving More Than One Level
Because break leaves only the innermost loop, searching a 2D table needs another way out of the outer loop. There are three common solutions:
| Technique | How | Comment |
|---|
| Flag variable | Set found = 1, break, and test the flag in the outer loop condition | Portable, a bit verbose |
| Function with return | Put the loops in a function and return as soon as the answer is known | Best: clear and testable |
| goto | goto done; jumps to a label after the loops | Acceptable for error cleanup and for leaving deep nests; MISRA C:2012 treats it as advisory (rule 15.1) |
nested_exit.c
#include <stdio.h>
#define ROWS 3
#define COLS 4
static const int grid[ROWS][COLS] = { { 5, 8, 1, 9 }, { 4, 7, 6, 2 }, { 3, 0, 8, 7 } };
/* 1. flag */
static void find_with_flag(int target)
{
int found = 0, fr = –1, fc = –1;
for (int r = 0; r < ROWS && !found; r++)
for (int c = 0; c < COLS; c++)
if (grid[r][c] == target) { found = 1; fr = r; fc = c; break; }
printf(“flag : %d at (%d,%d)\n”, target, fr, fc);
}
/* 2. return from a function */
static int find_with_return(int target, int *r_out, int *c_out)
{
for (int r = 0; r < ROWS; r++)
for (int c = 0; c < COLS; c++)
if (grid[r][c] == target) { *r_out = r; *c_out = c; return 1; }
return 0;
}
/* 3. goto */
static void find_with_goto(int target)
{
int r, c;
for (r = 0; r < ROWS; r++)
for (c = 0; c < COLS; c++)
if (grid[r][c] == target) goto found;
printf(“goto : %d not found\n”, target);
return;
found:
printf(“goto : %d at (%d,%d)\n”, target, r, c);
}
int main(void)
{
int r, c;
find_with_flag(6);
if (find_with_return(6, &r, &c)) printf(“return : 6 at (%d,%d)\n”, r, c);
find_with_goto(6);
find_with_goto(42);
return 0;
}
Output (verified by compiling and running)
flag : 6 at (1,2)
return : 6 at (1,2)
goto : 6 at (1,2)
goto : 42 not found
goto for error cleanup
The one use of goto that experienced C programmers accept without discussion is a single cleanup exit, as in the Linux kernel: acquire resources in order, jump to the matching label on failure, and release in reverse order.
goto_cleanup.c
#include <stdio.h>
static int init_system(int fail_at)
{
int rc = –1;
printf(“enable clock\n”);
if (fail_at == 1) goto out;
printf(“configure GPIO\n”);
if (fail_at == 2) goto undo_clock;
printf(“start UART\n”);
if (fail_at == 3) goto undo_gpio;
rc = 0;
printf(“system ready\n”);
return rc;
undo_gpio:
printf(” undo GPIO\n”);
undo_clock:
printf(” undo clock\n”);
out:
return rc;
}
int main(void)
{
printf(“-> %d\n”, init_system(0));
printf(“-> %d\n”, init_system(3));
return 0;
}
Output (verified by compiling and running)
enable clock
configure GPIO
start UART
system ready
-> 0
enable clock
configure GPIO
start UART
undo GPIO
undo clock
-> -1
6. Walking 2D Arrays in Memory Order
C stores a two-dimensional array in row-major order: all elements of row 0, then row 1 and so on. Nested loops should therefore run the column index in the inner loop, so memory is read sequentially. With the opposite order every access jumps a whole row ahead. On a Cortex-M4 with a small flash accelerator and no data cache the effect is moderate, but on devices with caches (Cortex-M7, STM32H7) and for DMA bursts it is large.
row_major.c
#include <stdio.h>
int main(void)
{
int m[3][4];
int *base = &m[0][0];
printf(“row-by-row access (column index inner): byte offsets\n”);
for (int r = 0; r < 3; r++)
{
for (int c = 0; c < 4; c++)
printf(“%3d”, (int)((char *)&m[r][c] – (char *)base));
printf(“\n”);
}
printf(“column-by-column access (row index inner): byte offsets\n”);
for (int c = 0; c < 4; c++)
{
for (int r = 0; r < 3; r++)
printf(“%3d”, (int)((char *)&m[r][c] – (char *)base));
printf(“\n”);
}
return 0;
}
Output (verified by compiling and running)
row-by-row access (column index inner): byte offsets
0 4 8 12
16 20 24 28
32 36 40 44
column-by-column access (row index inner): byte offsets
0 16 32
4 20 36
8 24 40
12 28 44
The first listing increases steadily by 4 bytes (one int); the second jumps by 16 bytes (one row of four ints) on every step. Use the first form unless the algorithm forces the other one (for example transposing).
7. Multiple Exits: Style and Standards
Every break or continue adds an exit or a jump to the loop and makes it harder to reason about. Safety standards are strict about this:
| Standard | Rule |
|---|
| MISRA C:2012 rule 15.4 | There should be no more than one break or goto statement used to terminate any iteration statement (advisory). |
| MISRA C:2012 rule 15.1 | The goto statement should not be used (advisory). |
| MISRA C:2012 rule 15.2 | A goto shall jump to a label declared later in the same function (required). |
| MISRA C:2012 rule 15.3 | Any label referenced by a goto shall be in the same or an enclosing block. |
| CERT C | Avoid the use of goto but allow cleanup patterns |
Practical advice: use break and continue when they make the code simpler, keep their number small, and document them. When a loop needs two or three different exits, move it into a function and use return.
8. Embedded Patterns with break and continue
| Pattern | Code idea | Why |
|---|
| Find the first free slot in a table | for (...) if (!slot[i].used) break; | Stop at the first hit |
| Skip invalid sensor values | if (v == INVALID) continue; | Keep averaging code clean |
| Process a frame until the end marker | while (1) { b = next(); if (b == END) break; ... } | Exit decided inside the loop |
| Retry with a limit | for (n = 0; n < 3; n++) { if (try_once()) break; } | Succeed or give up |
| Ignore comment lines in a config | if (line[0] == '#') continue; | Skip uninteresting input |
embedded_patterns.c
#include <stdio.h>
#include <stdbool.h>
typedef struct { bool used; int id; } slot_t;
static slot_t slots[4] = { { true, 10 }, { true, 11 }, { false, 0 }, { true, 13 } };
static int alloc_slot(int id)
{
int i;
for (i = 0; i < 4; i++)
if (!slots[i].used) break; /* first free slot */
if (i == 4) return –1; /* table full */
slots[i].used = true;
slots[i].id = id;
return i;
}
static int attempts;
static bool try_once(void) { attempts++; return attempts == 3; }
int main(void)
{
printf(“allocated slot %d\n”, alloc_slot(99));
printf(“allocated slot %d (table now full)\n”, alloc_slot(100));
int n;
attempts = 0;
for (n = 0; n < 5; n++)
if (try_once()) break;
printf(“operation succeeded on attempt %d (loop index %d)\n”, attempts, n);
const char *config[] = { “# comment”, “baud=115200”, “”, “# another”, “parity=none” };
for (int i = 0; i < 5; i++)
{
if (config[i][0] == ‘#’ || config[i][0] == ‘\0’) continue;
printf(“setting: %s\n”, config[i]);
}
return 0;
}
Output (verified by compiling and running)
allocated slot 2
allocated slot -1 (table now full)
operation succeeded on attempt 3 (loop index 2)
setting: baud=115200
setting: parity=none
9. Common Mistakes
Mistake 1 — expecting break to leave all loops: It leaves only the innermost loop or switch.
Mistake 2 — continue before the update in a while loop: Infinite loop. Update first, or use a for loop.
Mistake 3 — break inside a switch inside a loop: It leaves the switch only (Lesson 6.2).
Mistake 4 — reusing the same variable for inner and outer loop: for (i...) for (i...) resets the outer counter.
Mistake 5 — wrong inner limit: j < i instead of j < n changes the trip count completely.
Mistake 6 — using a variable after a break without checking why the loop ended: Test the index (i == n) or a flag to know whether the item was found.
Mistake 7 — deeply nested loops (4+ levels): Hard to read and slow; extract functions.
Mistake 8 — goto into a block that skips initialisation: Variables are left uninitialised; jump only forward to cleanup labels.
10. Best Practices for break and continue in Embedded C
- Use break for early exit from searches and for loops whose end is decided in the body.
- Use continue to skip invalid items instead of nesting the normal path inside an if.
- Update the loop variable before a continue in while loops, or use for.
- Keep nesting to two or three levels; extract a function for the inner part.
- Name the loop variables meaningfully when nesting (
row, col). - After a search loop, test why it ended: index equals n means not found.
- Limit the number of exits per loop and comment unusual ones.
- Use goto only for forward jumps to a single cleanup block.
Key Takeaways — Lesson 7.3
- break and continue in embedded C control the innermost loop only; break also leaves a switch.
- In a for loop continue still runs the update; in a while loop it jumps straight to the test.
- Nested loop counts multiply: 3 × 4 = 12 passes of the inner body.
- To leave several loops use a flag, a function return, or a forward goto.
- Row-major storage means the column index belongs in the inner loop.
- MISRA C advises at most one break or goto per loop.
Frequently Asked Questions
What is the difference between break and continue?
break ends the loop completely. continue ends only the current pass and starts the next one (running the for update and testing the condition).
Does break exit all nested loops?
No. break leaves only the innermost enclosing loop or switch. To leave several loops use a flag, return from a function or a goto to a label after the loops.
Why does my while loop hang after I added continue?
The continue probably skips the statement that updates the loop variable, so the condition never changes. Put the update before the continue or convert the loop to a for loop.
Is goto always bad?
Not always. A forward goto to a single cleanup label is a widely used and accepted C idiom for error handling. Avoid backward jumps and jumps into blocks. MISRA treats goto as advisory.
How many times does a nested 3 by 4 loop run its inner body?
12 times. The inner loop runs 4 times for each of the 3 passes of the outer loop.
Which index should be the inner loop in a 2D array?
The last index (columns), because C stores arrays in row-major order and the inner loop then reads memory sequentially.
Ready to practise?
Next: worked examples with table searches, prime numbers, patterns, matrices and frame parsing on real output.
2 Comments