goto & Labels
goto transfers control unconditionally to a labeled statement elsewhere in the same function. It is one of the oldest and most controversial features in C: extremely powerful, extremely easy to misuse, and largely avoided in modern application code — yet it still shows up deliberately in certain systems-level codebases for a small number of well-understood patterns.
Syntax
C
#include <stdio.h>
int main(void) {
int i = 0;
start:
if (i < 3) {
printf("i = %d\n", i);
i++;
goto start;
}
printf("done\n");
return 0;
}
// Output: i = 0, i = 1, i = 2, doneA label is just an identifier followed by a colon, placed before a statement.
goto label; jumps directly to that statement, skipping everything in between — including any loops, if blocks, or other structure that happens to lie on the way.Why goto Is Generally Discouraged
Unrestricted jumps make it hard to reason about a function by reading it top to bottom: control can leap forward or backward across blocks, bypassing the natural nesting that loops and conditionals normally provide. Code built around many
gotos tends to turn into what programmers call "spaghetti code"— a tangle of jumps where understanding what runs, and in what order, requires mentally tracing every possible path instead of simply reading downward.C
// Hard to follow: jumps back and forth make the control flow
// difficult to trace just by reading the code in order.
int x = 0;
loop1:
if (x >= 5) goto after;
printf("%d\n", x);
x++;
if (x == 3) goto skip;
printf("extra work\n");
skip:
goto loop1;
after:
printf("done\n");Warning
Avoid
goto for general control flow. Anything you can express with if, for, while, break, or continue should be, since those constructs keep control flow visually nested and predictable.The Legitimate Use Case: Breaking Out of Nested Loops
C has no labeled
break, so break can only exit the single innermost loop it is directly inside (see the Nested Loops page). When you genuinely need to abandon several levels of nested loops at once from deep inside them, goto jumping to a label right after the loops is the standard, accepted C idiom for it — cleaner than juggling multiple flag variables checked at every level.C
#include <stdio.h>
int main(void) {
int matrix[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int target = 5;
int found = 0;
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
if (matrix[row][col] == target) {
found = 1;
goto done; // exits BOTH loops at once
}
}
}
done:
if (found) {
printf("Found %d!\n", target);
} else {
printf("Not found.\n");
}
return 0;
}The Other Common Use Case: Centralized Cleanup
In C, there is no
try/finally or automatic destructor-based cleanup. When a function allocates several resources in sequence and any step can fail, jumping to a single cleanup label at the bottom of the function is a genuinely idiomatic pattern in real-world C codebases, including large ones like the Linux kernel. It avoids duplicating the cleanup code at every possible failure point.C
#include <stdlib.h>
#include <stdio.h>
int processFile(const char *path) {
FILE *file = NULL;
char *buffer = NULL;
int result = -1;
file = fopen(path, "r");
if (file == NULL) {
goto cleanup;
}
buffer = malloc(1024);
if (buffer == NULL) {
goto cleanup;
}
// ... use file and buffer to do real work ...
result = 0; // success
cleanup:
if (buffer != NULL) {
free(buffer);
}
if (file != NULL) {
fclose(file);
}
return result;
}Tip
Notice the pattern: every failure point jumps to the same
cleanup: label, and the cleanup code itself carefully checks each resource for NULL before releasing it, since not every failure path will have allocated every resource. This is the pattern to imitate if you use goto for cleanup.Note
A
goto can only jump within the same function, and in C you cannot jump into the scope of a variable-length array or jump into a block past a variable's initialization in a way that would leave it undefined. These restrictions limit how much damage a stray goto can do, but they do not make its use anything less than a deliberate, deliberate exception to normal structured control flow.Key Points
goto label; jumps unconditionally to a labeled statement anywhere else in the same function.
Overusing goto produces "spaghetti code" that is hard to trace -- prefer if/for/while/break/continue for normal logic.
A widely accepted use is breaking out of multiple nested loops at once, since C has no labeled break.
Another widely accepted use is jumping to a single centralized cleanup label on error, common in real C codebases like the Linux kernel.
goto can only jump within the same function, never into or out of a different one.