Are there any legitimate use-cases for "goto" in a language that supports loops and functions?

I've long been under the impression that goto should never be used if possible.

However, while perusing libavcodec (which is written in C) the other day, I was surprised to notice multiple uses of it.

Is it ever advantageous to use goto in a language that supports loops and functions? If so, why? Please provide a concrete example that clearly justifies the use of a goto.


Solution 1:

Everybody who is anti-goto cites, directly or indirectly, Edsger Dijkstra's GoTo Considered Harmful article to substantiate their position. Too bad Dijkstra's article has virtually nothing to do with the way goto statements are used these days and thus what the article says has little to no applicability to the modern programming scene. The goto-less meme verges now on a religion, right down to its scriptures dictated from on high, its high priests and the shunning (or worse) of perceived heretics.

Let's put Dijkstra's paper into context to shed a little light on the subject.

When Dijkstra wrote his paper the popular languages of the time were unstructured procedural ones like BASIC, FORTRAN (the earlier dialects) and various assembly languages. It was quite common for people using the higher-level languages to jump all over their code base in twisted, contorted threads of execution that gave rise to the term "spaghetti code". You can see this by hopping on over to the classic Trek game written by Mike Mayfield and trying to figure out how things work. Take a few moments to look that over.

THIS is "the unbridled use of the go to statement" that Dijkstra was railing against in his paper in 1968. THIS is the environment he lived in that led him to write that paper. The ability to jump anywhere you like in your code at any point you liked was what he was criticising and demanding be stopped. Comparing that to the anaemic powers of goto in C or other such more modern languages is simply risible.

I can already hear the raised chants of the cultists as they face the heretic. "But," they will chant, "you can make code very difficult to read with goto in C." Oh yeah? You can make code very difficult to read without goto as well. Like this one:

#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
        _-_-_-_-_-_-_-_
            _-_-_-_
}

Not a goto in sight, so it must be easy to read, right? Or how about this one:

a[900];     b;c;d=1     ;e=1;f;     g;h;O;      main(k,
l)char*     *l;{g=      atoi(*      ++l);       for(k=
0;k*k<      g;b=k       ++>>1)      ;for(h=     0;h*h<=
g;++h);     --h;c=(     (h+=g>h     *(h+1))     -1)>>1;
while(d     <=g){       ++O;for     (f=0;f<     O&&d<=g
;++f)a[     b<<5|c]     =d++,b+=    e;for(      f=0;f<O
&&d<=g;     ++f)a[b     <<5|c]=     d++,c+=     e;e= -e
;}for(c     =0;c<h;     ++c){       for(b=0     ;b<k;++
b){if(b     <k/2)a[     b<<5|c]     ^=a[(k      -(b+1))
<<5|c]^=    a[b<<5      |c]^=a[     (k-(b+1     ))<<5|c]
;printf(    a[b<<5|c    ]?"%-4d"    :"    "     ,a[b<<5
|c]);}      putchar(    '\n');}}    /*Mike      Laman*/

No goto there either. It must therefore be readable.

What's my point with these examples? It's not language features that make unreadable, unmaintainable code. It's not syntax that does it. It's bad programmers that cause this. And bad programmers, as you can see in that above item, can make any language feature unreadable and unusable. Like the for loops up there. (You can see them, right?)

Now to be fair, some language constructs are easier to abuse than others. If you're a C programmer, however, I'd peer far more closely at about 50% of the uses of #define long before I'd go on a crusade against goto!

So, for those who've bothered to read this far, there are several key points to note.

  1. Dijkstra's paper on goto statements was written for a programming environment where goto was a lot more potentially damaging than it is in most modern languages that aren't an assembler.
  2. Automatically throwing away all uses of goto because of this is about as rational as saying "I tried to have fun once but didn't like it so now I'm against it".
  3. There are legitimate uses of the modern (anaemic) goto statements in code that cannot be adequately replaced by other constructs.
  4. There are, of course, illegitimate uses of the same statements.
  5. There are, too, illegitimate uses of the modern control statements like the "godo" abomination where an always-false do loop is broken out of using break in place of a goto. These are often worse than judicious use of goto.

Solution 2:

There are a few reasons for using the "goto" statement that I'm aware of (some have spoken to this already):

Cleanly exiting a function

Often in a function, you may allocate resources and need to exit in multiple places. Programmers can simplify their code by putting the resource cleanup code at the end of the function, and all "exit points" of the function would goto the cleanup label. This way, you don't have to write cleanup code at every "exit point" of the function.

Exiting nested loops

If you're in a nested loop and need to break out of all loops, a goto can make this much cleaner and simpler than break statements and if-checks.

Low-level performance improvements

This is only valid in perf-critical code, but goto statements execute very quickly and can give you a boost when moving through a function. This is a double-edged sword, however, because a compiler typically cannot optimize code that contains gotos.

Note that in all these examples, gotos are restricted to the scope of a single function.

Solution 3:

Obeying best practices blindly is not a best practice. The idea of avoiding goto statements as one's primary form of flow control is to avoid producing unreadable spaghetti code. If used sparingly in the right places, they can sometimes be the simplest, clearest way of expressing an idea. Walter Bright, the creator of the Zortech C++ compiler and the D programming language, uses them frequently, but judiciously. Even with the goto statements, his code is still perfectly readable.

Bottom line: Avoiding goto for the sake of avoiding goto is pointless. What you really want to avoid is producing unreadable code. If your goto-laden code is readable, then there's nothing wrong with it.

Solution 4:

Well, there's one thing that's always worse than goto's; strange use of other programflow operators to avoid a goto:

Examples:

    // 1
    try{
      ...
      throw NoErrorException;
      ...
    } catch (const NoErrorException& noe){
      // This is the worst
    } 


    // 2
    do {
      ...break; 
      ...break;
    } while (false);


    // 3
    for(int i = 0;...) { 
      bool restartOuter = false;
      for (int j = 0;...) {
        if (...)
          restartOuter = true;
      if (restartOuter) {
        i = -1;
      }
    }

etc
etc

Solution 5:

Since goto makes reasoning about program flow hard1 (aka. “spaghetti code”), goto is generally only used to compensate for missing features: The use of goto may actually be acceptable, but only if the language doesn't offer a more structured variant to obtain the same goal. Take Doubt's example:

The rule with goto that we use is that goto is okay to for jumping forward to a single exit cleanup point in a function.

This is true – but only if the language doesn't allow structured exception handling with cleanup code (such as RAII or finally), which does the same job better (as it is specially built for doing it), or when there's a good reason not to employ structured exception handling (but you will never have this case except at a very low level).

In most other languages, the only acceptable use of goto is to exit nested loops. And even there it is almost always better to lift the outer loop into an own method and use return instead.

Other than that, goto is a sign that not enough thought has gone into the particular piece of code.


1 Modern languages which support goto implement some restrictions (e.g. goto may not jump into or out of functions) but the problem fundamentally remains the same.

Incidentally, the same is of course also true for other language features, most notably exceptions. And there are usually strict rules in place to only use these features where indicated, such as the rule not to use exceptions to control non-exceptional program flow.