I've long been under the impression that 'goto' should never be used if possible. While perusing libavcodec (which is written in C) the other day, I noticed multiple uses of it. Is it ever advantageous to use 'goto' in a language that supports loops and functions? If so, why?
In Perl, use of a label to "goto" from a loop - using a "last" statement, which is similar to break.
This allows better control over nested loops.
The traditional goto label is supported too, but I'm not sure there are too many instances where this is the only way to achieve what you want - subroutines and loops should suffice for most cases.
@jtyost2 : Why do you think that calling a function from another function using goto, would be simpler than exiting ?
Also, Couldn't we just call the function beta() in your example, get the results, and then return that out from the function ?
The problem with 'goto' and the most important argument of the 'goto-less programming' movement is, that if you use it too frequently your code, although it might behave correctly, becomes unreadable, unmaintainable, unreviewable etc. In 99.99% of the cases 'goto' leads to spaghetti code. Personally, I cannot think of any good reason as to why I would use 'goto'.
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. In really complex functions we relax that rule to allow other jump forwards. In both cases we are avoiding deeply nested if statements that often occur with error code checking, which helps readability and maintance.
Basically, due to goto
's defective nature (and I believe that this is uncontroversial), it is only used to compensate for missing features. So 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.
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 all 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.
In C# switch statement doest not allow fall-through. So goto is used to transfer control to a specific switch-case label or the default label.
For example:
switch(value)
{
case 0:
Console.Writeln("In case 0");
goto case 1;
case 1:
Console.Writeln("In case 1");
goto case 2;
case 2:
Console.Writeln("In case 2");
goto default;
default:
Console.Writeln("In default");
break;
}
Edit: There is one exception on "no fall-through" rule. Fall-through is allowed if a case statement has no code.
If so, why?
C has no multi-level/labelled break, and not all control flows can be easily modelled with C's iteration and decision primitives. gotos go a long way towards redressing these flaws.
Sometimes it's clearer to use a flag variable of some kind to effect a kind of pseudo-multi-level break, but it's not always superior to the goto (at least a goto allows one to easily determine where control goes to, unlike a flag variable), and sometimes you simply don't want to pay the performance price of flags/other contortions to avoid the goto.
libavcodec is a performance-sensitive piece of code. Direct expression of the control flow is probably a priority, because it'll tend to run better.
One of the reasons goto is bad, besides coding style is that you can use it to create overlapping, but non-nested loops:
loop1:
a
loop2:
b
if(cond1) goto loop1
c
if(cond2) goto loop2
This would create the bizarre, but possibly legal flow-of-control structure where a sequence like (a, b, c, b, a, b, a, b, ...) is possible, which makes compiler hackers unhappy. Apparently there are a number of clever optimization tricks that rely on this type of structure not occuring. (I should check my copy of the dragon book...) The result of this might (using some compilers) be that other optimizations aren't done for code that contains goto
s.
It might be useful if you know it just, "oh, by the way", happens to persuade the compiler to emit faster code. Personally, I'd prefer to try to explain to the compiler about what's probable and what's not before using a trick like goto, but arguably, I might also try goto
before hacking assembler.
#ifdef TONGUE_IN_CHEEK
Perl has a goto
that allows you to implement poor-man's tail calls. :-P
sub factorial {
my ($n, $acc) = (@_, 1);
return $acc if $n < 1;
@_ = ($n - 1, $acc * $n);
goto &factorial;
}
#endif
Okay, so that has nothing to do with C's goto
. More seriously, I agree with the other comments about using goto
for cleanups, or for implementing Duff's device, or the like. It's all about using, not abusing.
(The same comment can apply to longjmp
, exceptions, call/cc
, and the like---they have legitimate uses, but can easily be abused. For example, throwing an exception purely to escape a deeply-nested control structure, under completely non-exceptional circumstances.)
In C# switch statement doest not allow fall-through.
Actually...it does. You can do it either way. It says so on the page you linked.
Edsger Dijkstra, a computer scientist that had major contributions on the field, was also famous for criticizing the use of GoTo. There's a short article about his argument on Wikipedia.
In a Perl module, you occasionally want to create subroutines on the fly. The thing is, that once you have created the subroutine, how do you get to it. You could just call it, but then if the subroutine uses caller()
it won't be as helpful as it could be. That is where the goto
function can be helpful.
Here is a quick example of where it can be helpful:
sub AUTOLOAD{
my($self) = @_;
my $name = $AUTOLOAD;
$name =~ s/.*:://;
*{$name} = my($sub) = sub{
# the body of the code
}
goto $sub;
# nothing after the goto will ever be executed.
}
You can also use this form of goto
to provide a rudimentary form of tail-call optimization.
sub factorial($){
my($n,$tally) = (@_,1);
return $tally if $n <= 1;
$tally *= $n--;
@_ = ($n,$tally);
goto &factorial;
}
Just as well no one ever implemented the "COME FROM" statement....
I've written more than a few lines of assembly language over the years. Ultimately, every high level language compiles down to gotos. Okay, call them "branches" or "jumps" or whatever else, but they're gotos. Can anyone write goto-less assembler?
Now sure, you can point out to a Fortran, C or BASIC programmer that to run riot with gotos is a recipe for spaghetti bolognaise. The answer however is not to avoid them, but to use them carefully.
A knife can be used to prepare food, free someone, or kill someone. Do we do without knives through fear of the latter? Similarly the goto: used carelessly it hinders, used carefully it helps.
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.
I find it funny that some people will go as far as to give a list of cases where goto is acceptable, saying that all other uses are unacceptable. Do you really think that you know every case where goto is the best choice for expressing an algorithm?
To illustrate, I'll give you an example that no one here has shown yet:
Today I was writing code for inserting an element in a hash table. The hash table is a cache of previous calculations which can be overwritten at will (affecting performance but not correctness).
Each bucket of the hash table has 4 slots, and I have a bunch of criteria to decide which element to overwrite when a bucket is full. Right now this means making up to three passes through a bucket, like this:
// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if (slot_p[add_index].hash_key == hash_key)
goto add;
// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
goto add;
// Additional passes go here...
add:
// element is written to the hash table here
Now if I didn't use goto, what would this code look like?
Something like this:
// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if (slot_p[add_index].hash_key == hash_key)
break;
if (add_index >= ELEMENTS_PER_BUCKET) {
// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
break;
if (add_index >= ELEMENTS_PER_BUCKET)
// Additional passes go here (nested further)...
}
// element is written to the hash table here
It would look worse and worse if more passes are added, while the version with goto keeps the same indentation level at all times and avoids the use of spurious if statements whose result is implied by the execution of the previous loop.
So there's another case where goto makes the code cleaner and easier to write and understand... I'm sure there are many more, so don't pretend to know all the cases where goto is useful, dissing any good ones that you couldn't think of.
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
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.
- Dijstra's paper on
goto
statements was written for a programming environment wheregoto
was a lot more potentially damaging than it is in most modern languages that aren't an assembler. - 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". - There are legitimate uses of the modern (anaemic)
goto
statements in code that cannot be adequately replaced by other constructs. - There are, of course, illegitimate uses of the same statements.
- There are, too, illegitimate uses of the modern control statements like the
godo
abortion we can find right here in this very thread. These are often worse than judicious use ofgoto
.
Keep in mind while you're voting me down with one -1 after another that I have used goto
in my own (non-assembler) code precisely 3 times in the past 15-20 years.
I await the flood of outraged shrieks and -1 votes with bated breath.
I find the do{} while(false) usage utterly revolting. It is conceivable might convince me it is necessary in some odd case, but never that it is clean sensible code.
If you must do some such loop, why not make the dependence on the flag variable explicit?
for (stepfailed=0 ; ! stepfailed ; /empty/)