The page linked has a good rule of thumb for the gotos: don't use backward ones. All the "escape" uses of goto - breaking out of loops, jumping to the appropriate part of the cleanup code, emulating exceptions behavior are forward jumps.
Back in the day hand coded assembly had jumps into the middle of functions' bodies which was a space-saving idiom for things like default initialization - the beginning of the function had some initialization code but you could jump into the function body (and skip this initialization block), provided that you did your own init and set the proper registers yourself.
There is however one valid case for using backward gotos - it's the loop restart.
again:
for ( ..container.. )
{
...
if ( ... )
{
touch(container);
/*
* If touch() changes container in a contrived
* way, then restarting the loop is sometimes
* the cleanest option.
*/
goto again;
}
}
This obviously requires caution to not enter an infinite loop, but that's usually easier to guarantee than to reconcile the consequences of touch().
What almost all modern programmers, fortunately, are not exposed to are the goto-infested programs common during the time preceding this technical note. For one example, Fortran II was quite common at the time. IF statements were made up of a numerical test, and three branches: less, equal, greater. This made even simple programs quite difficult to understand.
Dijkstra, as noted in another comment by 'arocks, was a little alarmed at some taking the principle to the extreme. I am afraid that I was one of them for a while.
But the structured programming movement that followed and resulted in a great improvement in readability and in program reliability.
It still seems like there would be an opportunity for a smarter language here. Like, the reason that `goto` appears here is because the language does not know how to clean up the mess that it's making, thus the code requires humans to jump back and forth between the top and bottom making sure that each cleanup section properly cleans up the corresponding mess above. This is true of both the `if` and `goto` code; and we could do this much more easily if those pieces of code were side-by-side.
The scope keyword is for saying that if you get to this point, then do such and such at end of scope. scope(failure)/scope(success)/scope(exit), for exitting scope by exception/return/either. Is kind of nice.
You might be interested in looking at the Common Lisp "Conditions" system, which allows code outside of the stack to provide error-handling logic.
In general, though, I think it's more about presentation than the language per se. I've often felt that an IDE or editor that could selectively highlight or hide the different pathways through the code would be a very useful tool.
Dijkstra himself later clarified: "Please don't fall into the trap of believing that I am terribly dogmatical about [the go to statement]. I have the uncomfortable feeling that others are making a religion out of it, as if the conceptual problems of programming could be solved by a single trick, by a simple form of coding discipline!"
One thing about replacing goto with if's: CODE COVERAGE, specifically: BRANCH COVERAGE.
In situations where you have to prove 100% testing of all code, with full branch coverage and absolutely no dead code, the goto is preferred. This is simply because it is a faster instruction that does not depend on any other values being "valid" in order to function.
Why is that important? Bit-rot, folks. Like it or not, but we still have to ensure that the bit that is 1 is a 1 and not a 0, in this day and age - especially for safety-critical/life-dependent systems. The goto gives both full coverage capabilities (good for testing/certification) as well as providing one less path for radiation to be rotting bits.
(And before we get into the NAND/ECC discussion, remember: this is for software certification requirements.. it doesn't matter how hard your hardware is, what matters is how hard your software is ..)
Firstly, I really don't follow your argument about code coverage. If you need to ensure that some unit of code is executed, why can't you make it a function and call it?
Secondly, you have misunderstood what bit-rot is. Bit-rot is not cosmic rays flipping bits, it is a humorous term to describe what happens when code that used to work and has not been touched no longer works.
EDIT: It seems Wikipedia disagrees with me on the second point. My apologies.
I would prefer to rely on languages which can provably ensure all cases are handled than to rely on gotos to artificially improve a complexity metric. Really.
I found it enlightening to learn about the context in which the GOTO was first "considered harmful" by Dijkstra in 1968[1].
This is a period of time when the concept of higher level languages with specialist if-then, loops and return statements were still relatively new.
Most programmers in the world had cut their teeth on assembly language. In that model the natural way to think of programs is as a sequential series of addresses in memory that contain instructions.
Since all control structures are merely jumps to a new address, so why were all those grumpy ivory tower idiots so keen on abolishing goto?
Probably the biggest blow to the pro-goto camp in those days was the discovery of the "Structured Program Theorem" in 1966. It was proved that any computable algorithm can expressed in any higher level language that has sequencing of instructions, ability to select an execution path and iteration. The ability to jump is not required. For the times it was an critical discovery because it meant higher level language designers could proceed with the confidence that any HLL they designed with those three properties would be usable for any algorithm in theory.
Finally, Dijkstra (and the other European greats like Hoare and Wirth) had in mind a particular vision of what programming ought to be. He didn't see it so much as an engineering activity. He wanted to treat programs as mathematical objects. Controlling the scope of languages can make this more or less tractable. So for example the "one return from a function" rule goes back to this vision, but has since gone on to live a care-free life of its own.
It's also important to realize that the "go to" Dijkstra is complaining about isn't seen in modern languages. It's the goto of unstructured Basic and early Fortran where you're doing an unconstrained jump to some other part of the program source by line number. It has no real counterpart in C. Unless your entire program is one long main() function, forward and even backwards C goto within a function is a tamed HLL version that can be reasoned about.
I think Hoare said it in a talk I once heard, that control structures essentially give a higher-level meaning to the ordinary GOTO. In that sense I always understood the “considered harmful” paper as advice to replace GOTO by appropriate control structures where applicable.
Interesting insight, thanks. The way that "goto considered harmful" has been taken out of context and then gone viral reminds me of how Simonyi's original Hungarian prefix scheme was similarly misunderstood and then misapplied, to the detriment of many C and C++ codebases.
If (for whatever reason) you have to write code with support for exception handling compiled out, goto statements are incredibly useful. While not quite as syntactically nice as a try/catch/finally paradigm, the combined use of error checking macros and a "cleanup" label can rather elegantly emulate the control flow of exception handling where appropriate.
They're really a closer cousin to the RAII pattern common in C++ than exceptions. Exceptions usually imply action across functions, but here they're just trying to enforce end-of-scope invariants. So if you're writing C++ code with "exception handling compiled out" (such as with Embedded C++, if anyone still uses that) you can still leverage RAII to do a lot of that type of cleanup.
That said, "goto" gets a bad rap in my opinion. Anyone can parrot Dijkstra's "considered harmful" line, but few know the context that it came from. If you've seen large FORTRAN programs from that era, where "GO TO" was the main control flow choice and labels had to be integers you know how even simple algorithms could be made unreadable. Effectively Dijkstra was extolling ALGOL programers not to code as if they were writing FORTRTAN -- in a structured language "goto" is the control structure you need to use least.
Still, there are places where "goto" is the cleanest way to accomplish what you want to do, and you shouldn't fear using it. (Please pick a good name for the label though!) I've seen programmers write crazy twisty code with all sorts of strange temporary variables simply to avoid the use of the forbidden "goto" keyword. Clarity should be your aim, not religious purity.
Also remember that "break;" (except at the end of a switch case) "continue;" and even early "return;" are morally equivalent to the dreaded "goto;" when you think about it. They don't get the same bad press for some reason.
TeX is more or less one big pascal function with lots of gotos. While this makes it very hard to port to a more modular (and non-pascal) system, the source code (in typeset format: http://abel.math.umu.se/~lars/misc/tex.pdf) is very readable.
Well, for starters the semantics of allocate_memory are (in general) such that a SUCCESS == false condition mean that no memory was allocated. So freeing the memory would be an error itself. This is me being a bit pedantic, but it is important to note that the error condition for resource allocation is generally "there is nothing from this step to deallocate". This can turn into an "off by one" error in cleanup if you aren't careful.
Second, say your allocate_memory is successful... your next n steps look like:
There are two things to note here. First of all, the if lines get very long when you have to do all that unwinding at the loop. This is ugly and annoying, but itself not a problem. Second, and this is a code smell, you've gone and seriously repeated yourself. Now every time you add a resource allocation, you have to update the unwinding in several places. There is more room for error, more chance of subtle issues in order creeping in. etc. This pattern is a way to avoid it.
The author addresses doing the whole chain in nested ifs in the original article.
[+] [-] praptak|13 years ago|reply
Back in the day hand coded assembly had jumps into the middle of functions' bodies which was a space-saving idiom for things like default initialization - the beginning of the function had some initialization code but you could jump into the function body (and skip this initialization block), provided that you did your own init and set the proper registers yourself.
[+] [-] huhtenberg|13 years ago|reply
[+] [-] wglb|13 years ago|reply
Dijkstra, as noted in another comment by 'arocks, was a little alarmed at some taking the principle to the extreme. I am afraid that I was one of them for a while.
But the structured programming movement that followed and resulted in a great improvement in readability and in program reliability.
[+] [-] drostie|13 years ago|reply
[+] [-] rwmj|13 years ago|reply
http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html
[+] [-] twoodfin|13 years ago|reply
[+] [-] Garoof|13 years ago|reply
[+] [-] jacques_chester|13 years ago|reply
In general, though, I think it's more about presentation than the language per se. I've often felt that an IDE or editor that could selectively highlight or hide the different pathways through the code would be a very useful tool.
[+] [-] arocks|13 years ago|reply
[+] [-] primitur|13 years ago|reply
In situations where you have to prove 100% testing of all code, with full branch coverage and absolutely no dead code, the goto is preferred. This is simply because it is a faster instruction that does not depend on any other values being "valid" in order to function.
Why is that important? Bit-rot, folks. Like it or not, but we still have to ensure that the bit that is 1 is a 1 and not a 0, in this day and age - especially for safety-critical/life-dependent systems. The goto gives both full coverage capabilities (good for testing/certification) as well as providing one less path for radiation to be rotting bits.
(And before we get into the NAND/ECC discussion, remember: this is for software certification requirements.. it doesn't matter how hard your hardware is, what matters is how hard your software is ..)
[+] [-] jstanley|13 years ago|reply
Secondly, you have misunderstood what bit-rot is. Bit-rot is not cosmic rays flipping bits, it is a humorous term to describe what happens when code that used to work and has not been touched no longer works.
EDIT: It seems Wikipedia disagrees with me on the second point. My apologies.
[+] [-] jacques_chester|13 years ago|reply
[+] [-] zvrba|13 years ago|reply
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.103....
[+] [-] bcoates|13 years ago|reply
[+] [-] jacques_chester|13 years ago|reply
This is a period of time when the concept of higher level languages with specialist if-then, loops and return statements were still relatively new.
Most programmers in the world had cut their teeth on assembly language. In that model the natural way to think of programs is as a sequential series of addresses in memory that contain instructions.
Since all control structures are merely jumps to a new address, so why were all those grumpy ivory tower idiots so keen on abolishing goto?
Probably the biggest blow to the pro-goto camp in those days was the discovery of the "Structured Program Theorem" in 1966. It was proved that any computable algorithm can expressed in any higher level language that has sequencing of instructions, ability to select an execution path and iteration. The ability to jump is not required. For the times it was an critical discovery because it meant higher level language designers could proceed with the confidence that any HLL they designed with those three properties would be usable for any algorithm in theory.
Finally, Dijkstra (and the other European greats like Hoare and Wirth) had in mind a particular vision of what programming ought to be. He didn't see it so much as an engineering activity. He wanted to treat programs as mathematical objects. Controlling the scope of languages can make this more or less tractable. So for example the "one return from a function" rule goes back to this vision, but has since gone on to live a care-free life of its own.
[1] http://www.u.arizona.edu/~rubinson/copyright_violations/Go_T...
[+] [-] bcoates|13 years ago|reply
[+] [-] ygra|13 years ago|reply
[+] [-] mattmanser|13 years ago|reply
Oh-oh spaghetti-oh!
Not that you should waste your time programming in a GOTO dependant language, but it becomes pretty obvious after a couple of days why GOTO sucks.
The opening article is pretty vacuous imo. That's not really a legitimate use, it's just that C++ doesn't support finally.
[+] [-] tragomaskhalos|13 years ago|reply
[+] [-] bigfrakkinghero|13 years ago|reply
[+] [-] bodyfour|13 years ago|reply
That said, "goto" gets a bad rap in my opinion. Anyone can parrot Dijkstra's "considered harmful" line, but few know the context that it came from. If you've seen large FORTRAN programs from that era, where "GO TO" was the main control flow choice and labels had to be integers you know how even simple algorithms could be made unreadable. Effectively Dijkstra was extolling ALGOL programers not to code as if they were writing FORTRTAN -- in a structured language "goto" is the control structure you need to use least.
Still, there are places where "goto" is the cleanest way to accomplish what you want to do, and you shouldn't fear using it. (Please pick a good name for the label though!) I've seen programmers write crazy twisty code with all sorts of strange temporary variables simply to avoid the use of the forbidden "goto" keyword. Clarity should be your aim, not religious purity.
Also remember that "break;" (except at the end of a switch case) "continue;" and even early "return;" are morally equivalent to the dreaded "goto;" when you think about it. They don't get the same bad press for some reason.
[+] [-] justincormack|13 years ago|reply
[+] [-] patrickg|13 years ago|reply
[+] [-] kvinnako|13 years ago|reply
if(allocate_memory() != SUCCESS) { free_memory(); return error; } ...
The other examples provided make a better use case of GOTO.
[+] [-] sophacles|13 years ago|reply
Second, say your allocate_memory is successful... your next n steps look like:
There are two things to note here. First of all, the if lines get very long when you have to do all that unwinding at the loop. This is ugly and annoying, but itself not a problem. Second, and this is a code smell, you've gone and seriously repeated yourself. Now every time you add a resource allocation, you have to update the unwinding in several places. There is more room for error, more chance of subtle issues in order creeping in. etc. This pattern is a way to avoid it.The author addresses doing the whole chain in nested ifs in the original article.
[+] [-] askimto|13 years ago|reply