6 ms·
GOTO (2000)
- whitten 3y agoDoes this mean that compilers have given up on processing GOTO if it is written directly by a person, but are fine in analyzing code generated in Intermediate code from GOTO-less code ?
- bregma 3y agoNot at all. The compilers I'm familiar with (and my dayjob is maintaining compilers for a commercial OS) all just emit a phi node and construct basic blocks for all flow control constructs. By the time you're deep in the IR you have no idea if it was a do-loop, a for-loop, a while loop, a loop with continue and break statements, a series of deeply-nested if-statements, and aggregate initializer, or a goto. The people who religiously avoid the use of gotos are more often than not just cargo-culters.
- Findecanor 3y agoOn the contrary. Many passes in a compiler are simpler if the control-flow graph is reducible [1], which it is guaranteed to be if the program had been written in structured programming without any gotos. It is just that "goto" is closer to assembly code, and therefore used in intermediary code representations inside the compiler for that reason. But if the compiler had made sure beforehand that the control flow is reducible (and don't break that in an intermediary pass), then it will remain so. I find that most uses of gotos in source code are still reducible control-flow, but which had just not been expressible using structured programming constructs in the language. For example jumping out of nested loops, "for-else" and error handling. It is very rare that you see "spaghetti code" in practice. 1: https://en.wikipedia.org/wiki/Control-flow_graph#Reducibility https://en.wikipedia.org/wiki/Control-flow_graph#Reducibilit...
- justadolphinnn 3y agoGOTO users are holding society back
- peterhull90 3y agoIn my youth I had a ZX spectrum which had BASIC with line numbers and no renumber command. Sometimes when adding code I'd simply run out of line numbers so had to GO TO an unused block of line numbers, put the new code there and GO TO just after the original code. I've never quite recovered from that.
- bregma 3y agoMy first job out of university was maintaining a FORTRAN IV program on a PDP-11. The only control structures in that language was IF..GOTO and the arithmetic GOTO. You can still write readable half-decent code with that, with discipline. A DO loop is still superior for readability. The horrors of BASIC with its spaghetti of GO TO or its mess of PEEKs and POKEs are a justification for permabanning that style of programming -- but a decade of typing in listings from magazines inspired the generation the brought us the web and pocket phones. Maybe it wasn't such a bad thing after all.
- pklausler 3y agoYou had a FORTRAN compiler that didn't have DO loops? I'm dubious.
- shawn_w 3y agoFORTRAN IV (1962) predates DO, which if Wikipedia is to be trusted, was introduced in FORTRAN 66.
- pklausler 3y agoFORTRAN (no numbers!) had DO loops in the first release of the language. No subroutines, only statement functions, but there absolutely were DO loops! The ability of the compiler to perform what are now basic loop optimizations on them is what sold the users on automatic compilers.
- mmaniac 3y agoReminds me of ROM hacking. Overwriting an instruction inside a function with a branch to unused memory and jumping back later is an easy way to get extra space for the patch you want to write.
- deleted 3y ago[deleted]
- boomlinde 3y agoGood "Go To Statement Considered Harmful" considered harmful take. I frequently use goto in C to jump to resource cleanup code in case of an error. Always turns out more readable than the alternatives. I think I may even have used it in Go once, but I can't remember what for. Go's defer keyword obviates the above use of goto, and labeled breaks almost any other case I can think of.
- buescher 3y agoI wouldn't die on that hill but I agree and have done the same. In fact I would argue that if you find yourself in arguments about the practice it is a good sign your team is at a level where you should in fact prohibit all use of goto. IIRC at least one of the revisions of MISRA specifically allows for using goto to break forward to a cleanup-and-return block, but others don't.
- JonChesterfield 3y agoYou might find splitting the functions into two works well. The outer one tries to allocate whatever resources are needed, the inner one assumes they have been allocated. Ends up looking something like (copied from one of my C codebases) static regex_compare_t regex_canonical_equivalent_with_structures( regex_cache_t *cache, stringtable_index_t x, stringtable_index_t y, intstack_t *stack_arg, intmap_t *map_arg) { // fairly complicated code using those structures } regex_compare_t regex_canonical_equivalent(regex_cache_t *cache, stringtable_index_t x, stringtable_index_t y) { if (x.value == y.value) { return regex_compare_equal; } intstack_t stack = intstack_create(256); if (!intstack_valid(stack)) { return regex_compare_out_of_memory; } intmap_t map = intmap_create(16); if (!intmap_valid(map)) { intstack_destroy(stack); return regex_compare_out_of_memory; } regex_compare_t res = regex_canonical_equivalent_with_structures(cache, x, y, &stack, &map); intstack_destroy(stack); intmap_destroy(map); return res; }
- teo_zero 3y agoYours seems a good practice. But apart from having concentrated it in the outer function, you have not reduced the cleanup burden. In fact, the outer function might grow out of control if the number of resources increases. In your example, should you allocate foo and bar after stack and map, you need to remember to add the respective destroy's to all failure path, and possibly in the correct reversed order (which you don't show in your code for map & stack, by the way). You'll soon find yourself with 4 identical calls to intstack_destroy() to maintain. Instead, I'll advocate for a cleanup section at the end similar to cleanup: if (bar) bar_destroy(bar); if (foo) foo_destroy(foo); if (map) intmap_destroy(map); if (stack) intstack_destroy(stack); return res; And I would replace all the if (!something) with, e.g. if (!map) { res = regex_out_of_memory; goto cleanup; }
- kevindamm 3y agoI disagree about the examples of when a go-to is useful: > - Using a single goto and single label to exit several levels of scope. Better handled by putting the nested scope in a function and using `return`. > - Using a goto in the middle of complex construction to short cut to the top of a loop. Use `continue`. > - Starting a highly optimized do { ... } while loop that is best initiated by jumping into the center first. (Because your compiler is not cooperating.) This one's tricky, first be sure that the optimization is actually needed. Then, with a quick glance at DRY for apology put a copy of the second half of the loop before the `do`. > - Extensions of duff's device. Oh come on. The original Duff's device is not intuitive when first approaching it, and there is pretty much one use for it. I wouldn't even suggest to juniors that they be aware of this trick at all, tbh. It's a curiosity but I balk at it being a justification for adding gotos to make your own custom extension of it. See above, check that you really need such a severe optimization. You likely don't.
- OskarS 3y ago>> - Using a single goto and single label to exit several levels of scope. > Better handled by putting the nested scope in a function and using `return`. I dunno, that seems pretty silly to me. It's a perfectly valid use case of goto to jump out of deeply nested loops, and refactoring it to break out the inner loop in a function seems like pointless busy-work that ultimately makes the code harder to read, just so you can avoid a goto.
- clauderoux 3y ago@OskarS I agree with you... In fact, I have been avoiding gotos as a matter of principle for years and now reading the different discussions, I think I was misled. This case is very, very common. You have 3 intertwined loops and you want to get out. The traditional solution is to add some intermediate variables to propagate the end of the loops, with a cascade "if (toto) break;" which are far from being exquisitely elegant... Using a function for that is really adding some more complexity and some more stuff on the stack for nothing.
- 3y ago
- OskarS 3y agoI've been working my way through the latest volume of The Art of Computer Programming (Volume 4B, released last year) which is about combinatorial algorithms. One of the very first algorithms he presents Algorithm B, "Basic backtrack". It's like a generic backtracking algorithm that you could use to solve combinatorial problems (e.g. n-queens, things like that). Every time I've seen this algorithm presented, it's used recursion, because that's a very natural way to express backtracking. But Knuth doesn't, he basically never uses recursion in his TAoCP algorithms, he expresses his algorithms in a "flow chart" style, not assuming things like stacks or subroutines (in fact, one of the exercises in the chapter is to convert Algorithm B to a recursive version, which he then quickly dismisses in the answer to the exercise as "elegant, and works find for small problems" but fundamentally not very high performance and not really clearer than the non-recursive version). So, I set down to implement Algorithm B in a generic way and used it to solve n-queens. The way Knuth has written it, it can be straightforwardly translated using `goto`s (it has lines like "if so-and-so, go to step B4, else B3"), but being the modern, structured programmer that I am, i tried to convert it to structured programming using loops and if statements. It was doable, but when benchmarking, my versions were always slightly slower than Knuth's original "goto" formulation, and honestly I don't think they were more readable either. You had to add a bunch of variables to keep some state around that was implicit in Knuth's version. The recursive version was definitely more readable, but Knuth was of course correct, you pay a performance penalty for that readability. It was a real eye-opener. In the hands of a master, "goto"s really do have a place, and when well-deployed, they can sometimes simply be superior to "structured" code using loops, ifs and recursion.
- darig 3y ago[dead]
- mrkeen 3y agoI think the 'debate' only makes sense in a sufficiently low-level language, at which point you're already taking safety into your own hands. In a higher-level language, it's as silly as asking if you should avoid explicit malloc/free when writing SQL. How would the Rust compiler deal with goto? You could dodge ownership and start operating on data before/after it's created/deleted. A try-with-resources/using in Java/C# would no longer offer its guarantees. How about async blocks in general? Do you just wander into another thread's code (but keep your own stack frame?) Lambdas? Closures? People probably > regurgitate the age old argument against the use of goto because there's no new arguments for it.
- neonsunset 3y agoC# goto does not let you break `using var ...` guarantees or violate scopes of other control flow constructs.
- ChrisMarshallNY 3y agoIt is really a post about software dogma. GOTOs are just one of the oldest ones, but these days, for example, we have polymorphism, and OO, in general, and, for some folks, anything that is \(!$HOLY_MANNA) is bad. In my work, I tend to mix brand-new techniques with very old ones (probably older than some of the folks reading these words of prose). Writing Solid Code was written in 1992. Many of its techniques have been integrated into toolchains, some are outdated, and some are still every bit as valid, today, as they were, then. I will say that reading it was a watershed, for me.
- JonChesterfield 3y agoThe goto of goto considered harmful is not the goto of C. Somehow this subtlety seems to be broadly missed. The goto of Dijkstra jumps to somewhere else in the program. You're somewhere in the call stack and decide to scribble on some registers and jump to some arbitrary location. Like longjmp with the restrictions taken off. As in across function boundaries. When you're writing assembly that works exactly as it it sounds like it would. The goto of C jumps to somewhere else in the same function with a bunch of compiler warnings if you manage to bypass an initialisation. You can make control flow more complicated within a single function, sure. Goto in C is not a significant problem. Jumping between different functions in assembly is however legitimately confusing. This is something of a pet annoyance because various languages or coding standards decide to outlaw the friendly easy goto as it has such a scary reputation. This makes some algorithms much more complicated to express and others inherently slower.
- samatman 3y agoI don't believe I came up with this one, but I haven't been able to source it: for a modern audience, "Go To Statements Considered Harmful" should probably be titled "Structured Programming Considered Beneficial". Structured programming was (comparatively) new, and advocating for it made sense, given that FORTRAN and COBOL were the major languages in use, and (at the time) didn't exactly encourage the style.
- kazinator 3y agoSomeone made a similar claim in HN recently. I reviewed Dijkstra's paper and it doesn't bear out the claim. The C goto falls squarely under the scope of the paper. Dijkstra's paper is not strictly focused on a global, machine-language goto that can branch anywhere in the address space. In fact, Dikjstra mention that he would also like to banish statement like while B repeat A and repeat B until A, because we have recursion, which makes them unnecessary. He lets them slide only for reasons of "realism" and because such loops are analyzable with inductive techniques.
- JonChesterfield 3y agoThere is imprecision in my wording. The paper was about removing goto from high level programming languages. The context in which people were starting to view goto with suspicion was that of ~1968, at which point I believe the baseline assumption was that goto was a branch to anywhere as that's what assembly gives you. However it was before my time and I'm inferring from partial information. I think the distinction between goto as rendered in C and goto as rendered in machine code is significant. It's the same argument in favour of structured programming in both cases. The gap between structured programming and local goto within a function is smaller than in the assembly case.
- nyrikki 3y agoNote, the common reason people fail to comprehend what Dykstra was explaining, is because people don't realize they are almost universally adopting one of the three major programming paradigms. While people are familiar with Functional and OOP, most forgot or don't know about structured programming. Because it has proven to be good by default almost universally. Look at the COBOL ALTER statement or GCC computed GOTO to see what the issues were. While there are Böhm–Jacopini theorem purists that argue break and return, it is the law of diminishing returns. Break and return may be syntactic sugar for GOTO, but they don't have the same problem of the unrestricted transfer of control that was and is considered harmful.
- skinowski 3y agoGOTO is useful when used in the right context. Heavy use of exceptions are more of a problem today.
- kazinator 3y ago> Update: I recently looked at the book "Compilers Principles, Techniques, and Tools". In it, it describes the intermediate languages that programs are compiled to in which typically all branches are transformed to goto and if ... goto constructions. The idea being that optimizations are done after the program has been transformed to a loopless goto riddled intermediate program. That seals it for me -- I say use goto whenever it makes sense. That same book also teaches something important: programs based only on control structures like if and while generate goto graphs that meet a certain important property. All non-forward gotos in the graph are backwards gotos, dominated by the statement to which they branch. A situation like this isn't generated by control structures; it requires goto: if (condition0) { statement1; label0: statement2; } if (condition1) { goto label0; } What happens now that statement2 is reachable in a way that doesn't require/entail the execution of statement1. The "goto label0" is not a true backwards goto. Backwards gotos return to nodes which dominate the goto statement; i.e. go back to a point in the program that had to be visited in order to reach the goto. Here, label0 doesn't dominate the goto at all. The goto can be reached without reaching that label.
- GnarfGnarf 3y agoI haven’t written a GOTO since 1992, when I needed a common exit to free memory that couldn’t be accommodated on the tiny 4K stack of Turbo C / 16-bit. I have never experienced the need for a ‘goto’ otherwise. Now, 1970’s COBOL, that was another story.