Introduction
Both C and C++ include the goto statement that goes (jumps) to the statement having the given label within the same function, for example:
if ( disaster )
goto error;
// ...
error:
// handle the error
As you probably know, goto has a bad reputation stemming chiefly from Edsger Dijkstra’s infamous go to Statement Considered Harmful (1968) letter wherein he wrote in part:
For a number of years I have been familiar with the observation that the quality of programmers is a decreasing function of the density of go to statements in the programs they produce. More recently I discovered why the use of the go to statement has such disastrous effects, and I became convinced that the go to statement should be abolished from all “higher level” programming languages ….
Notice that he says the quality of programmers, not programs, decreases the denser the use of goto is; hence not only are programs with lots of gotos bad programs, but if you’re the author of such a program, you’re a bad programmer.
Not only was Dijkstra’s letter extremely influential, but its title has spurred an entire considered harmful template for other essays and papers including “Considered Harmful” Essays Considered Harmful.
As a curious historical note, Dijkstra’s original title was “A Case Against the Goto Statement,” but it was Niklaus Wirth, the then-editor of CACM, that changed the title. We’ll never know if Dijkstra’s letter would have had the same degree of influence had it kept its original title, but probably.
For a detailed analysis of Dijkstra’s letter, see Dijkstra’s Letter, Annotated.
What led to Dijkstra writing his letter was the (over)use of goto in programs written in either early versions of Fortran (1957–1966) and BASIC (1964–). To be fair, those programming languages had no other way to jump elsewhere in a program which is in part what led to increased support for the structured programming movement. (The term “structured programming” was coined by Dijkstra.)
Even Kernighan and Ritchie in their also extremely influential The C Programming Language wrote in part (1st ed., §3.9, pp. 62–63):
C provides the infinitely-abusable
gotostatement, and labels to branch to. Formally, thegotois never necessary, and in practice it is almost always easy to write code without it. We have not usedgotoin this book. Nonetheless, we will suggest a few situations wheregotos may find a place.
Those few situations are what this post is about.
Legitimate Uses
Aborting Processing
One legitimate use for goto was shown at the outset of this post, namely error handling, but where either clean-up code is necessary, such as freeing memory, closing files, or unlocking mutexes, or simply printing the same error message. The legitimacy increases with the number of gotos going to the same label.
For example, this code from my include-tidy project contains:
void cli_options_init( int *pargc, char const **pargv[] ) {
// ...
for (;;) {
// ...
if ( option->has_arg == required_argument ) {
if ( optarg == NULL )
goto missing_arg;
SKIP_WS( optarg );
if ( optarg[0] == '\0' )
goto missing_arg;
}
// ...
switch ( opt ) {
// ...
case ':':
goto missing_arg;
}
}
// ...
return;
missing_arg:
fatal_error( EX_USAGE,
"\"%s\" requires an argument\n",
get_opt_format( opt == ':' ? optopt : opt )
);
}
While it is possible to eliminate the uses of goto here, doing so would require either much more deeply nested if statements or the introduction of flags, both of which would make the code harder to understand.
For a case where you have to do specific clean-up, such as calling free, a defer statement like the one found in Go would be better if it were added to C:
void read_file( FILE *file );
void *const buf = malloc( BUF_SIZE );
defer free( buf ); // hypothetical defer in C
// ...
}
Then, no matter* how you return from the function, free will be called.
There actually is a proposal to add defer to C. Currently, it’s slated to be added to C29, the next standard version of C.
* Well, there are cases where it does matter, but that would be going too far into the weeds on
deferthat this post isn’t about. For such details, read the proposal.
In C++, there are destructors, so this use for goto is largely eliminated.
Nested Loop or switch Statements
While both C and C++ contain the break and continue statements, break breaks out of only the most lexically enclosing loop (while, do, or for) or switch; continue continues only the most lexically enclosing loop. There’s also this article that’s good.
In some cases, however, you want either to break or continue the not most lexically enclosing loop or switch. Unlike in either Java or Rust, in C or C++, you can only use a goto. The example already given above is also an example for this case.
There is also a proposal to add named loops to C such that you could do something like:
loop:
for ( int i = 0; i < j; ++i ) {
switch ( n ) {
// ...
case 99:
break loop; // hypothetical named loop
}
}
That is, a label in front of a loop can be referenced by a break or continue in addition to goto. Currently, it too is slated to be added to C29.
Restrictions
Prior to C23, a label could not be attached to a declaration (because a declaration is not a statement in C; it is in C++):
if ( disaster )
goto error;
// ...
error: // illegal < C23; OK in C23 or C++
int err_code;
The common work-around is to insert a null statement, i.e., a single semicolon:
error:; // note ';' -- OK in all C versions
int err_code;
All C versions forbid jumping past the declaration of a “variably modified type,” i.e., variable length arrays (VLAs) or pointers to such:
void f( unsigned n ) {
// ...
goto skip; // error: can't skip past ...
// ...
int a[n]; // ... VLA
int (*p)[n]; // ... or pointer to VLA
skip:
// ...
}
C++ has more restrictions that C regarding use of goto, specifically that you can’t jump past a declaration if its type has a “non-trivial constructor” or has an initializer:
goto skip;
// ...
int x; // OK: no initializer
std::string s; // error: has non-trivial constructor
int y = 42; // error: has initializer
// ...
skip:
// ...
Advice
Here’s some advice for using (or not using) goto:
- Of course prefer
break,continue,return, or any loop when those do exactly what you want. - If you would need to add one of multiple flags, multiple tests, or more deeply nested code, consider a
gotoinstead. - Personally, I think it’s a good practice only to go to a line that’s later in a function rather than earlier, either immediately after a loop or at the end of a function. If you want to go earlier, it likely means you’re looping, so use an actual loop instead.
- Put clean-up code that’s the target of a
gotoat the end of a function where it more clearly stands out.
Conclusion
I think Dijkstra’s wish to abolish goto entirely goes a bit far. Certainly, you want to avoid either inappropriate or over-use of gotos lest you end up with spaghetti code. While a lot of code can (and should) be written easily without goto, a justified use of an occasional one won’t make you a bad programmer.
Top comments (0)