The last major use of goto in C is to recreate what many languages solve via some form of destructor, defer statement, with statement, or some other mechanism that has the compiler insert specified cleanup code on all exiting paths from a block. If you use such a mechanism, then it becomes impossible for the programmer to accidentally skip over such cleanup code, whereas a C function using goto might have the programmer write an early return instead of the goto by accident and skip it. And this is why there's a strong anti-goto sentiment: we have better tools [2] to achieve the same ends that are less error prone than goto is. And when you have the better tools available, why shouldn't you ban the worse tools?
[1] The one thing you can do with goto that you can't do with labeled break/continue is create irreducible control flow graphs (essentially, a loop with two entry points, thus having no dominator within the loop itself). On the other hand, I'm pretty sure that murdering a programmer who creates an irreducible control flow graph is considered justifiable homicide, so no harm is lost by outlawing it.
[2] In languages other than C, though.
defer in Go is more like a try/finally statement in other languages, there's a guarantee that the deferred code will run even when there's a panic in the intervening code:
handler = open(something);
defer close(handler);
// something causing a panic
Should be the same as: handler = open(something);
try {
// something causing an exception
} finally {
close(handler);
}
This is not necessarily true of goto, you have to be more deliberate and disciplined (and discipline doesn't scale) to ensure that when your code hits an error you go to the cleanup section, if you miss even a single case and return early, you will not do the cleanup. Both defer and finally eliminate that potential error.The interesting thing (to me) about defer is that it promotes making explicit what's implicit in languages like C++ with RAII. In C++ with RAII, handler would be closed up at the end of the lexical scope but it's implicit, Go makes you explicitly state that you intend for it to be closed using a defer. But the defer, stylistically not by requirement, being near the initialization is, arguably, clearer than the goto or the finally statement. It's also less error prone, you can scan the function and see that someone left out a defer close(handler). But if you use a cleanup block at the end (either with try/finally or with gotos) you have to jump back and forth between the top and bottom of the function in order to see that everything has been cleaned up and in the correct order.
That said, it's worth noting that difference in practice since Go is, as far as I know, the only mainstream language with defer as a major and commonly used construct.
There is no need to use GOTO when a language has functions/procedures and exceptions.
Of course it makes sense in Assembly or more rudimentary languages, or in specific cases for speed optimization (getting out of multiple loops).
However, I must admit, sometimes I'm sick of the way Go "deals" with errors so I resort to GOTO to manage HTTP errors in my HTTP request handlers.
Exceptions use a dynamic scope to decide where to jump to, so, in the general case, it's a lot harder to understand how they will affect program behavior by simply reading the code. That's a major reason why using exceptions for non-exceptional control flow is widely considered to be evil.
... and defer
Which “defer”? The go panic/defer pair is, while structured differently, functionally equivalent to exceptions.
try { ... }
catch (SomeException e) { ... }
catch (SomeOtherException e) { ... }
finally { /* cleanup code */ }
Pretty much every language with exceptions has an equivalent of that finally block, a portion of code guaranteed to run whether there is an exception or not.Defers in Go act like that finally block, but permit you to attach them at the point where the initialization they clean up was written. This can help with longer code blocks, but it's fundamentally the same as the above (with or without the catches). So if you use defer (the following is not really Go, but captures the idea):
function echo_file (filename) {
f = open(filename);
if nil == f { return; } // simple error handling, just don't do anything
defer close(f);
for line in f {
print(line);
}
}
function echo_file (filename) {
f = open(filename);
if nil == f { return; }
try {
for line in f {
print(line);
}
}
finally {
close(f);
}
}
The former is much shorter and clearer, especially for straightforward cleanup code, but is the same as the latter in what it does. Whether an exception or panic occurs during the read/print loop or not, the file will always be closed in both cases.exception handling mechanisms (try/except/finally) are idiomatic for that in languages with them (try/finally, particularly, because it assures cleanup whether or not an unhandled exception occurred); defer offers equivalent power with slightly different structure; you don't need both for any purpose either will do.
There is no need for exceptions either. Everything can be done with error code.
Just because there are redundant mechanisms to achieve same result does not mean one of them must be abolished.
The usual example of when goto saves more trouble than it causes is breaking out of a nested loop early. You can avoid goto in that scenario with a sufficient number of flags, but flag-heavy code tends to be harder to read than a couple apposite gotos. Even better is Perl-style named blocks, though.
This assessment cuts pretty close to the gist of (my reading of) Dijkstra's famous paper that kicked off the anti-goto sentiment, once you take some time to digest the entirety of the paper and consider the context in which it was being written.
I'd also like to throw out there, though, that the "go to" statement he describes is a "go to" statement that functions like the one in programming languages from before 1968. Which is a goto statement that, I'm guessing, very few of us have ever used. Early versions of BASIC - the "20 GOTO 10" variety - are maybe the most well-known example nowadays. If you've spent much time with a Commodore 64 or a 1st-generation IBM PC, you're probably familiar.
He wasn't criticizing C's goto. Not just because C hadn't been invented yet. His most lucid, damning criticisms of goto in higher-level languages can't really be leveled against C's goto statement, because C's goto simply doesn't exhibit the features he's criticizing.
That sort of thing can make safely modifying the second subroutine require knowing if any of its labels are used elsewhere in the program.
It is basically impossible to reason locally when developing like that, unless you first verify that the only uses are local. (Or have applied strict coding standards such that only labels specifically identified can be used as non-local goto targets, etc.)
That sort of thing was extremely normal in say early assembly programming where your code size was so restricted, you needed to reuse code whenever possible. And many early languages brought that sort of capability along with it.
I'd expect Dijkstra's feeling on C's goto would probably be like: Probably a good idea to avoid it when the other control structures work well, but barring silliness like trying to code a large program in a single function, using C's goto for cases not well handled by the other control structures is fine, since it is constrained to local use only.
The words have straightforward meanings, unfortunately, so they've since been recontextualized into settings where their prescriptions can still be followed, but the rationale doesn't hold up at all.
Many of those who started with HLLs probably don't realise the machine can be far more flexible and powerful beyond their constraints. It's all about using the appropriate level of abstraction.
As a note on this, I think the novel you're talking about is Georges Perec's 'La Disparition'. Supposedly the lack of an 'e' is symbolic of the loss of his family in the Holocaust. Anyway, I always thought one of the greatest feats of the English language was the guy who translated the novel to English, also omitting the letter 'e'. Absolutely mindblowing talent.
Unreal! I'm going to have to go read a passage or few. Thanks for mentioning it.
Beyond structured programming you might have object oriented programming that uses restricted access to the object's members to enforce certain parities on them that make the object's behavior easier to reason about. Or functional programs that restrict the persistent mutable state and make programs easier to reason about in another way.
This means it is ripe for abuse by amateurs and fools and from that comes its reputation.
It's a shame that dogmas are developed against the use of such things instead of creating a culture of appropriate use.
You see it against say php, jquery, and even gof design patterns these days.
It's bullshit. Just because some buffoons do stupid things with something powerful it doesn't mean all people that use it are buffoons
> Why would an otherwise sane person write code with "rep" and without ever using "jmp"?
They wouldn't. Nobody is suggesting that you should avoid `jmp` in assembly. I think maybe you missed the point? The whole "avoid goto" thing is talking about higher level languages than assembly that provide proper flow control primitives like `if`, `for`, `switch` and so on.
And if we're being silly, you only really need mov[1].
The goal is to improve quality by making the code better structured and easier to follow.
For instance, MISRA C rule 15.1 that states that goto should not be used explains: "Unconstrained use of goto can lead to programs that are unstructured and extremely difficult to understand". Indeed, in practice that's often the case.
Btw that is a Cert C recommendation to use goto for RAII!
Take a look at the Linux kernel sometime, goto is everywhere there too https://www.kernel.org/doc/html/v4.10/process/coding-style.h...
How does it know which were allocated and which were not? If you are talking about memory allocation, then you could simply deallocate all the pointers provided they were initially set to NULL. Freeing a NULL pointer is well-defined in C.
Also, free(NULL) is not well-defined in C, and other resource types aren't so easy to check. delete nullptr in C++ is well-defined, but you rarely need it.
Oh yes it is: https://stackoverflow.com/questions/1938735/does-freeptr-whe...
It can also be a bit self-documenting, or be a place-holder if there is for example stuff to delete in a loop (elements of an array) before releasing the pointer itself (the array).
I don't know, when you read it, sometimes it feels natural and meaningful, and sometimes you know that the author didn't know that free() handles NULL all right.
[0] https://wiki.sei.cmu.edu/confluence/display/c/MEM12-C.+Consi...
I know - that's why I explicitly mentioned memory.
It's a "best practice" in the correct sense of the term. I.e. in most cases it is both not needed and clearer, more structured code may be written without using it.
Of course, as for anything, it is useful in a very few cases, emphasis on "very few".
"clearer" and "structured" doesn't matter from a compiler point of view. Hence the only ones that can judge "clearer" or "structured" is us, human beings, and that's why I think it's subjective.
"goto" is seen as "incorrect" only because of mere subjective reasons: "it's not clear", "it's not structured", "I read in a forum that it's bad", etc.
The keyword exists, it works as intended, and it's safe to use when used correctly, as all the things around programming languages.
To me, Rust code is a mess and I cannot get used to it. It's not "clearer" even if it's proven to be bug free and all that.
It is pragmatic, practical, and clear to have a rule not to use it. This forces people to make the effort to structure their code. Now, if it so happens one day that the result is indeed worse than using goto then a conscious decision can be made to make a specific exception. Otherwise, this is the sort of thing that will creep up and cause unless discussions.
In the same spirit, you can also enjoy Knuth's "structured programming with goto statements" for a clean programming style whose control flow is based on goto.
Sure, but constrained uses are fine, right? So why blanket-ban it?
Rule 15.2 The goto statement shall jump to a label declared later in the same function (forward gotos only)
Rule 15.3 Any label referenced by a goto statement shall be declared in the same block, or in any block enclosing the goto statement (can't jump a scope level lower than current [jumping in an if block from outside] or from 2 identical but separate scopes [from one if to another independent if])
Personally, I like the Linux kernel model for goto which fits these constraints. Reading code like a shopping list and jumping to the proper cleanup point is much easier to read than having 3-4 if else levels and having to scroll the screen to make sure resources are cleaned at all levels, etc. Nothing beats making the mental model of code smaller to keep in my head and readability in my book so I'll take that approach over applying holy war-ladden absolute rules.
Note that using gotos also jives well with Rule 15.5 requiring a single exit point in functions (Advisory).
Edit: as panax wrote somewhere in this thread, CERT-C rules even recommend it
The ban (in the case of C, anyway) is almost always the result of someone's completely subjective opinion that gotos are bad.
While it is true MISRA does not intend a blanket ban on these (and a blanket ban is contrary to the rationale for these rules), it often leads to a blanket ban and when these two in particular happen together it leads to awful code. I've seen this from multiple customers in different industries.
That has three practical benefits: It's clear (which is an underrated quality), it forces people to structure their code accordingly, and it prevents endless discussions (another very valuable quality). Otherwise, in my experience the use of goto tends to creep up and every occurrence leads to discussions in code review.
So it's not that those decisions are subjective is that they are practical and workable. Especially, if you are defining practices for contractors to follow it is beneficial to keep those rules simple and clear as they will form contractual obligations.