The Statement Everyone Warns You About

goto jumps to a labeled statement elsewhere in the same function, no conditions, no structure, just a jump. It is one of the 32 keywords, it sits on every syllabus, and it comes wrapped in more folklore than any other line of C. This lesson teaches what it actually does, why structured code replaced it, and the one narrow job for which serious C programmers still reach for it.

Syntax

A label is an identifier followed by a colon, attached to a statement; goto transfers control to it:

Labels are visible throughout their function, so a goto may jump forward or backward, into scopes and out of them, and this exact freedom is the problem. The label's traditional placement at the left margin makes jump targets easy to spot.

Why Structured Code Won

Programs built from goto in the pre-structured era became what the industry named spaghetti code: control flow that hops unpredictably, where understanding any line requires knowing every jump that can land near it. The structured statements you now own, if, else, switch, and the loops arriving next chapter, each have one entrance and predictable exits, so code reads top to bottom and reasoning stays local. Edsger Dijkstra's 1968 letter "Go To Statement Considered Harmful" is the canonical citation, an exam favorite worth attaching to the idea: every goto pattern in ordinary logic has a structured replacement that reads better.

The example above is honest proof: the goto bad_input version works, but the chapter's standard guard, printing and returning inside the if, says the same thing with no jump at all. Choosing between branches is if's job; selecting by value is switch's; repeating is a loop's. Ordinary logic never needs the jump.

The One Respectable Use

One pattern keeps goto in real codebases, including the Linux kernel: forward jumps to shared cleanup. A function that acquires several resources in sequence, memory, files, locks, must release the ones it acquired before returning on any failure. C has no exceptions to unwind for you; without goto, each failure path repeats the growing cleanup, or nests conditions ever deeper. The shape, in sketch:

    r1 = acquire_first();
    if (r1 failed) { goto out; }
    r2 = acquire_second();
    if (r2 failed) { goto release_first; }
    /* ... work ... */

release_first:
    release(r1);
out:
    return status;

Every jump is forward, every jump lands on cleanup, and the function still has a single exit. Once dynamic memory and files arrive in later chapters, this pattern will reappear with real resources and earn its keep. Until then, the summary is: cleanup convergence is the exception; everything else is spaghetti.

Exam Answers, Calibrated

Syllabus questions ask three things. What does goto do: unconditional jump to a label in the same function. Why is it discouraged: it destroys the local, top-to-bottom readability that structured statements guarantee, producing spaghetti code; cite Dijkstra. Is it ever acceptable: yes, forward jumps to error-cleanup code in languages without automatic unwinding, and any other use in your answer should be rewritten with structured statements. That calibrated last clause, not a flat "never", is what separates a memorized answer from an understood one.

Key Takeaways

  • goto label; jumps unconditionally to label: in the same function, forward or backward.
  • Structured statements replaced it because one-entrance, predictable-exit code keeps reasoning local; Dijkstra's 1968 letter is the standard citation.
  • Every ordinary use has a structured replacement: branches are if/switch, repetition is a loop.
  • The one accepted pattern is forward jumps to shared error cleanup, which later chapters demonstrate with real resources.
  • On exams: define it, explain the spaghetti objection, and concede the cleanup exception.