Skip to content
VLSI Mentor

C Part I · Module 4 · Decisions and Control Flow

goto: What It Does and Where It Is Used

Chapter 4.6 ended with a problem it could not solve. break leaves exactly one loop, so a search nested two levels deep has no direct way out — it has to set a flag, test that flag in the outer condition, and hope nobody later adds a third level. C does have a statement that transfers control to any point you name, and it has the worst reputation of anything in the language. Both halves of that reputation are worth taking seriously: the warnings are not superstition, and the statement is not forbidden either, because real C written by careful people uses it in two specific places. This chapter covers exactly what it does, what it cannot do, why the structured constructs are better almost everywhere, and the two patterns where it is the clearer answer.

Intermediate16 min readCPart IFundamentalsControl Flow

Part I · Module 4 · Chapter 4.8 · goto: What It Does and Where It Is Used

1. What It Does

A label is a name followed by a colon, placed before a statement. A goto transfers control to it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    goto finished;

    /* statements here are skipped */

finished:
    printf("arrived\n");

That is the entire mechanism. There is no condition and no return — control arrives at the label and carries on from there.

Two limits on it are worth stating immediately, because they are narrower than the reputation suggests.

A goto can only jump within the function it appears in. Labels have function scope: they are visible everywhere inside their function and nowhere outside it. You cannot jump from one function into another. (C has a separate mechanism for leaving one function and resuming in another, and it is not goto; it is also not something you should reach for.)

Jumping does not undo anything. It is not a return and not an exception. Objects that were being used are not tidied up, and anything the skipped statements would have done simply does not happen.

2. Why the Reputation Is Deserved

The case against unrestricted jumps is not that they are inelegant. It is about what a reader can know.

With the constructs in this module, the possible paths through a piece of code are local and finite. Looking at a loop, you know control enters at the top and leaves either through the condition or through a break you can see in the body. Looking at an if, you know exactly two continuations exist. You can reason about a page of code by reading that page.

A goto removes that guarantee in one direction: control can arrive at a label from anywhere in the function. To know how a labelled statement can be reached you must search the whole function, and a program with jumps scattered through it has a path structure that cannot be read off the page at all — which is where the phrase spaghetti code came from, and the complaint was accurate.

So the default is not in doubt:

Use the structured constructs. if, switch, while, for, break and continue express almost everything, and they express it in a form a reader can check locally.

What follows are the exceptions, and they are exceptions because they are the two shapes those constructs genuinely do not cover.

3. Pattern One: Leaving More Than One Loop

Chapter 4.6 §4 ran this search and got the wrong answer:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
trying 1 x 1
...
trying 3 x 4          <- the first match, found here
...
trying 4 x 3          <- and overwritten here
last pair found: 4 x 3

Fifteen attempts, and the reported pair was the last match rather than the first, because break ended only the inner loop and the outer one kept going.

Here is the same search with a goto:

Azvya Education Pvt. Ltd.VLSI Mentor
leaving both loops at once
#include <stdio.h>

int main(void)
{
    int target = 12;
    int found_a = 0;
    int found_b = 0;

    for (int a = 1; a <= 4; a++) {
        for (int b = 1; b <= 4; b++) {
            printf("trying %d x %d\n", a, b);
            if (a * b == target) {
                found_a = a;
                found_b = b;
                goto found;
            }
        }
    }

found:
    printf("first pair found: %d x %d\n", found_a, found_b);

    return 0;
}

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
trying 1 x 1
trying 1 x 2
trying 1 x 3
trying 1 x 4
trying 2 x 1
trying 2 x 2
trying 2 x 3
trying 2 x 4
trying 3 x 1
trying 3 x 2
trying 3 x 3
trying 3 x 4
first pair found: 3 x 4

Twelve attempts instead of fifteen, and the answer is the pair the search actually found first.

That difference is worth being precise about, because it is easy to file this under efficiency. The break version was not a slower way of getting the right answer — it got a different answer. A search that is specified to report the first match and reports the last one is incorrect, and the flag-based repair in Chapter 4.6 §4 exists to fix a correctness bug, not to save three multiplications.

Six stages. First, a match is found inside the inner loop. Second, a break statement ends the inner loop only. Third, the outer loop then advances and the inner loop starts again from its first value, so the search continues after it has already succeeded. Fourth, a goto statement transfers control directly to its label instead. Fifth, control arrives at the labelled statement placed after both loops. Sixth, nothing between the goto and the label is executed, so the search stops at the first match.break and goto out of a nested loop01A MATCH IS FOUND, INSIDE THE INNER LOOPthe search is finished02BREAK ENDS THE INNER LOOP ONLYone level, never two03THE OUTER LOOP ADVANCES AND THE INNER ONE RESTARTSthe search carries on after succeeding04GOTO TRANSFERS CONTROL TO ITS LABEL INSTEADpast both loops at once05CONTROL ARRIVES AT THE LABELLED STATEMENTplaced after both loops06NOTHING IN BETWEEN IS EXECUTEDthe search stops at the first match
Figure 1 — the two exits from a nested loop. A break ends only the loop immediately containing it, so the outer loop advances and the inner loop runs again from its starting value; a search written that way keeps going after it has found what it was looking for. A goto transfers control directly to its label, past both loops at once, and nothing between the jump and the label is executed.

Compare the two repairs honestly. The flag version adds an object, gives the outer condition a second unrelated job, and still requires the reader to verify that the flag is tested before anything else happens. The goto version adds one jump whose destination is a label a few lines below, visible on the same screen.

The case for goto here is that it is forward, local, and single-purpose: it jumps down, to a label in sight, for one reason. Those three properties are what keep the §2 objection from applying.

A jump that goes backward, or to a label pages away, or that several different conditions use for different reasons, has none of them and earns the reputation.

4. Pattern Two: Cleanup in Reverse Order

The second pattern is the one that makes goto common in serious C, and it comes from a shape that appears constantly: a sequence of steps, each of which can fail, where failing at step three means undoing steps two and one — in reverse order, and only the ones that actually succeeded.

Expressing that with if and else alone produces a staircase that grows one indentation level per step, with the undo logic duplicated at every failure point. Labels express it directly, because the cleanup is already in reverse order on the page:

Azvya Education Pvt. Ltd.VLSI Mentor
staged setup with cascading cleanup
#include <stdio.h>

int main(void)
{
    int stage_a_ready = 1;
    int stage_b_ready = 1;
    int stage_c_ready = 0;

    int status = 0;

    if (!stage_a_ready) {
        status = 1;
        goto done;
    }
    printf("stage A opened\n");

    if (!stage_b_ready) {
        status = 2;
        goto close_a;
    }
    printf("stage B opened\n");

    if (!stage_c_ready) {
        status = 3;
        goto close_b;
    }
    printf("stage C opened\n");

    printf("all three stages running\n");

    printf("stage C closed\n");
close_b:
    printf("stage B closed\n");
close_a:
    printf("stage A closed\n");
done:
    printf("finished with status %d\n", status);

    return status;
}

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
stage A opened
stage B opened
stage B closed
stage A closed
finished with status 3

Stage C failed. Stages B and A had succeeded, so they were closed — in that order, B before A — and stage C was not closed, because it was never opened. Nothing else in the program had to know which stages were open.

The mechanism is that the labels fall through into each other, in the same way Chapter 4.3's case labels did. goto close_b closes B and then continues into close_a, which closes A and continues into done. Each failure point jumps to the label for the last thing that succeeded, and the cascade handles the rest. There is exactly one copy of each closing step, and one exit from the whole sequence.

Notice also that the successful path does not jump at all. It runs off the bottom of stage C's work straight into printf("stage C closed\n") and then into the same cascade. Success and failure share the cleanup rather than duplicating it, which is what "single exit" means in practice.

5. The One Hazard to Know

A goto can jump forward past a declaration. The object still exists — but its initialiser never ran:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int skip = 1;

    if (skip) {
        goto after;
    }

    int value = 42;

after:
    printf("value is %d\n", value);
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
warning: variable 'value' is used uninitialized whenever 'if' condition is
    true [-Wsometimes-uninitialized]

This is exactly the hazard Chapter 4.3 §6 described for switch, and it has the same cause: control entered the middle of a block, so the statements above the entry point did not run. Reading value here is the bug Chapter 2.3 named — not a way to get 42, and not a way to get zero.

The habit is the same one too: declare and initialise before any jump that could skip past it, or put the declaration inside a block the jump does not enter. In §4's program, every object used after a label is declared and initialised at the very top, before the first goto exists — which is not an accident.

6. When Not to Use It

Three uses are not defensible, and recognising them is most of what separates disciplined goto from the kind that earned the reputation.

Jumping backward to repeat something. That is a loop. Write a loop — it states the three obligations from Chapter 4.4 in a form a reader can check, which a backward jump does not.

Jumping into the middle of a loop or an if body. The construct's own entry point exists so that readers know how control got there. Arriving somewhere else destroys that, and §5's hazard is waiting.

Using it as a general-purpose shortcut. The moment a function contains several labels reached from several places for several different reasons, the local-reasoning property §2 described is gone, and so is the justification.

The two patterns in §3 and §4 both avoid all three. They jump forward only, to a label in the same screenful, for one purpose, from a small number of places that all mean the same thing. Those are the conditions under which goto is an answer rather than a mess.

7. What You Should Be Able to Say Now

  1. goto label; transfers control to a labelled statement in the same function.
  2. Labels have function scope; you cannot jump between functions, and goto is not a return.
  3. Jumping undoes nothing — skipped statements simply do not run.
  4. The objection to it is about local reasoning: a label can be reached from anywhere in its function.
  5. The structured constructs are the default because the paths through them are visible on the page.
  6. Leaving nested loops is the first legitimate use — and the break version of Chapter 4.6's search was wrong, not merely slower.
  7. Cascading cleanup is the second: labels in reverse order, each failure jumping to the last successful stage, one exit.
  8. The successful path should fall into the same cleanup, not duplicate it.
  9. Jumping past an initialiser leaves the object uninitialised — -Wsometimes-uninitialized.
  10. Defensible jumps are forward, local and single-purpose. Backward jumps, jumps into a construct, and general shortcuts are not.

8. Exercises

Exercise 1 — predict the output. Do not compile yet.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int n = 0;
goto skip;
n = 5;
skip:
printf("%d\n", n);

Then say why the assignment did not run, and whether the compiler is likely to mention it.

Exercise 2 — trace the cascade. For §4's program, give the complete output when stage_b_ready is 0 and the other two are 1. Then do the same for all three ready, and for stage_a_ready of 0.

Exercise 3 — explain the ordering. In §4, stage B is closed before stage A. Explain which property of the label layout makes that happen, and what would go wrong if close_a were written above close_b.

Exercise 4 — compare the repairs. Write Chapter 4.6 §4's nested search twice: once with a flag, once with a goto. For each, say how many objects exist purely to control the loops, and what a reader must check to be sure the search stops at the first match.

Exercise 5 — find the hazard. Say what is wrong with this, which diagnostic you would expect, and give two repairs.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int mode = 1;
if (mode == 1) {
    goto report;
}
int scale = 10;
report:
printf("scale %d\n", scale);

Exercise 6 — classify four jumps. For each, say whether it is defensible under §6, and why: (a) a forward jump out of three nested loops to a label just below them; (b) a backward jump to the top of a block to repeat it; (c) a jump from inside an if body to a cleanup label at the end of the function; (d) a jump into the middle of a for body from outside the loop.

Exercise 7 — rewrite without it. Take §4's program and write it using only if and else, with no labels. Count the indentation levels and the number of places each closing step appears. Then say which version you would rather modify to add a fourth stage.

Exercise 8 — defend a policy. A project forbids goto entirely. Give the strongest argument for the rule, the strongest argument against it, and say what narrower rule would capture most of the benefit with less of the cost.

9. Summary

  • goto label; transfers control to a labelled statement within the same function. Labels have function scope.
  • It undoes nothing and is not a return.
  • Its reputation comes from a real property: a label can be reached from anywhere, so local reasoning stops working.
  • Leaving nested loops is a legitimate use — and Chapter 4.6's break version of that search was not slow, it was wrong.
  • Cascading cleanup in reverse order is the other, and it is the shape most mature C codebases are built from. Module 9 revisits it with real resources.
  • Jumping past an initialiser leaves the object uninitialised, exactly as in a switch.
  • Defensible jumps are forward, local and single-purpose. Everything else is why the rule exists.

That completes the control flow a C program needs. You can now choose between paths, dispatch on a value, repeat work with a termination argument you can defend, interrupt a loop in either of the two useful ways, recognise the bugs those constructs attract, and leave a nested structure cleanly. Every program in this module has nevertheless been a single block of statements inside main, growing longer with each chapter — and the staged setup in §4 is already close to the length at which a reader stops being able to hold it all at once. The next module introduces the unit that solves that, and it brings a rule about what happens to your values when you use it that is the single most consequential thing to understand about calling code in C.

Standards & specifications

Governing standard
ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)

Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C Programming curriculum.