[HN Gopher] Goto
___________________________________________________________________
Goto
Author : bruce343434
Score : 217 points
Date : 2021-10-21 11:58 UTC (11 hours ago)
(HTM) web link (beej.us)
(TXT) w3m dump (beej.us)
| bodyfour wrote:
| Probably coming too late to the discussion, but commenting
| anyway...
|
| The issue I think is that people today read "GOTO Considered
| Harmful" without really understanding the world at the time.
|
| Other than simple integer FOR loops, basically all control-flow
| in the FORTRAN of those days was accomplished with GOTO -- and to
| numbered lines, not labels. Things we take for granted in all
| languages today like { code blocks } didn't exist.
|
| Indeed the first thing you do when you try to understand code of
| that era is to print it out, get your markers out, and start
| drawing arrows all over the green-bar paper so you can start to
| make some sense of where control flow is going.
|
| In that world, yes, GOTO was a big problem and its use was
| rightly replaced with the structured control flow that we all use
| today. But that world has also been nearly gone for 30 years now.
| It's completely unrelated to using a C "goto" statement with a
| well-chosen label name in order to accomplish some otherwise-
| awkward control flow.
|
| On projects I've worked on I tend to develop a reputation for
| heavy goto use and I do so unapologetically. Sometimes it
| honestly is the cleanest way to implement something and I don't
| think it should be feared one bit.
|
| Just because it was a bad idea to do _everything_ with a GOTO 60
| years ago shouldn 't mean don't do _some things_ with goto today.
| moffkalast wrote:
| Yeah to those still adamant about it, if you've ever used a:
|
| - function call
|
| - if sentence
|
| - any kind of loop
|
| You've used a GOTO under the hood. I hope you can live with
| yourself, you monster :)
| masklinn wrote:
| > You've used a GOTO under the hood.
|
| That it's under the hood is the point of using constrained
| control-flow control. It's significantly easier to reason
| about `if` and `while` and `for` than about `goto`. That is
| basically the same reason functional programming have more
| HoFs than `fold`. You can do everything with `fold` _and that
| 's a problem_ because it creates way more cognitive overhead
| for the reader, they can't know what the code is trying to
| achieve until they get all the details, nor is the compiler
| able to check anything.
|
| Likewise goto.
| agent327 wrote:
| You put a smiley so I suppose it's possible you know better,
| but this kind of shallow, thoughtless critique is just
| painful to read, especially when following such a well-
| presented, historically accurate description of the debate
| surrounding goto. This is especially true given that Dijkstra
| himself refutes your argument, such as it is, in the opening
| paragraph of his seminal paper.
| brundolf wrote:
| But there's a reason we come up with sanctioned, constrained
| concepts built atop more powerful lower-level ones.
| Constraints in code benefit maintainability, correctness, and
| sometimes even performance.
|
| "Yeah to those still adamant about it, if you've ever used
| statically-typed code, you've used untyped assembly under the
| hood. I hope you can live with yourself, you monster :)"
| 1-more wrote:
| a case statement is the one where the GOTO peeks out the
| most, imo.
| andi999 wrote:
| The real hidden gotos are:
|
| - state machines - early return of a function
| GrumpyYoungMan wrote:
| What's even worse is that, at the machine language level,
| condition and unconditional jumps are all that there are;
| we've all been using GOTOs constantly without even knowing
| it. :)
| GrumpyYoungMan wrote:
| > " _The issue I think is that people today read "GOTO
| Considered Harmful" without really understanding the world at
| the time. ... Just because it was a bad idea to do everything
| with a GOTO 60 years ago shouldn't mean don't do some things
| with goto today._"
|
| Every time this comes up, I always encourage people to read an
| excellent analysis by David Tribble " _Go To Statement
| Considered Harmful: A Retrospective_ " that goes over
| Dijkstra's essay line by line and explains what it means in a
| more modern context: http://david.tribble.com/text/goto.html It
| really should be required reading in CS programs.
|
| I've used and still use goto in the few niches it makes sense
| to, primarily error handling and multi-level break in C/C++.
| westcort wrote:
| What I want is a language that consists ONLY of GOTO
| statements. The Turing tape concept indicates that it should be
| possible.
| schoen wrote:
| Subtract and branch if negative is more or less that:
|
| https://en.wikipedia.org/wiki/One-
| instruction_set_computer#S...
| 0xffff2 wrote:
| Can you give an example of where you've used goto in C or a
| C-like language? Or a rough estimate of the number of times
| you've found goto to be the best solution?
|
| I agree with your post in principle, but in practice I can't
| think of a single example where I've used a goto that wasn't
| eventually refactored to something better that didn't have the
| goto.
|
| Edit: Probably the most common example (and one given early in
| the article) is deeply nested loops. This is a good example of
| where I'm very unlikely to use goto. Almost certainly, I will
| prefer to hide one or more of the inner loops inside of a
| function call.
| throwaway09223 wrote:
| There's a good real world example of goto for progressive
| cleanup here: https://github.com/square/pam_krb5_ccache/blob/
| master/pam_kr...
| [deleted]
| codr7 wrote:
| https://github.com/codr7/ampl/blob/main/src/ampl/eval.cpp
| slaymaker1907 wrote:
| Replying to infiniterand, a table of function pointers is
| going to generally have way more overhead than an indirect
| goto. An indirect goto is essentially a single assembly
| jump instruction. This is also often faster than a while
| loop plus switch since it is friendlier to the branch
| predictor.
| InfiniteRand wrote:
| I'm sure there's probably a reason for doing things this
| way, but it seems like that code would make more sense as a
| function pointer table and a series of functions. Was that
| not preferred for aesthetic reasons or for some specific
| technical reason?
| codr7 wrote:
| Speed.
|
| Your suggestion adds indirect memory access plus function
| call, for every instruction.
|
| I've tried every other way I can think of, and nothing
| runs faster from my experience.
| bodyfour wrote:
| > deeply nested loops. [...] I will prefer to hide one or
| more of the inner loops inside of a function call.
|
| And I would too... but sometimes there are too many local
| variables involved to do that cleanly. And, ultimately, if
| you are making your code less clean to avoid using a goto
| then you avoided unwisely.
|
| > Can you give an example of where you've used goto in C or a
| C-like language?
|
| Well there have been several fairly normal examples cited by
| others. Let me set out my stall and present a more
| controversial one.
|
| Suppose I have a switch() block (which is sorta a glorified
| goto already!) and I have two cases that have the same
| epilogue. For example: switch
| (get_next_thing_to_do()) { case ACTION_A:
| do_action_a(); goto common_post_action_cleanup;
| case ACTION_B: do_action_b();
| common_post_action_cleanup: cleanup_something();
| z = nullptr; x = x->next; // yadda yadda
| break; case ACTION_C: do_action_c();
| // NOTE: we don't need to do the "cleanup" here
| break; }
|
| Now the first thing you should try is to put all of the
| shared code in its own function. Again, this isn't always
| practical if what you need to do effects a lot of local
| variables. It also means that those "cleanup" steps are now
| implemented far away from the rest of the logic which might
| hurt comprehensibility on its own.
|
| Another option is to just suck it up, repeat the code twice,
| and then just trust that the compiler will clean it up.
| First, that won't be as attractive if you have to do it 10
| times instead of twice. Second, you're now violating the DRY
| principal which to me is sacrosanct. Experience has taught me
| that if someone updates the cleanup code for ACTION_A they
| _will_ forget to update ACTION_B at the same time. If you
| care that the two cases always end with the same epilogue you
| need to have a single copy of the code.
|
| You can try to get back into compliance with DRY by using
| something like a C macro, but now you're not winning the war
| against ugly code.
|
| Finally you could just put the cleanup code after the
| switch(), either by retesting the enum value (requiring it to
| be stored in a temporary variable) or by adding some other
| new cleanup_needed boolean flag. But now we're _adding_
| control flow that wasn 't there before. I personally believe
| that doesn't help readability or maintainability. You're now
| just adding more variables to understand and probably getting
| worse generated assembly to boot.
|
| While many programmers will look at this and feel vaguely
| icky about the presence of a "goto" if you really stop and
| _look_ at the block of code it 's actually quite
| readable...at least as long as the label and the goto are
| near each other. It is immediately apparent that two "states"
| are sharing some code and why.
|
| This little example is obviously made up. However I've spent
| a lot of my career writing things like hand-crafted state
| machines and situations exactly like this (where two "states"
| are different but share the same epilogue) comes up _all_ the
| time. State machine code like this is famously hard to read
| but often sharing code via goto like this is the least-bad
| option for expressing non-hierarchical control flow.
| anfelor wrote:
| Another example not covered on the article is tail call
| optimisation. GCC is not very good at it and manually adding
| it using goto can sometimes speed code up by ~20%. Very
| useful when writing down a complex recursive algorithm that
| you can't easily express as a loop.
| davidwf wrote:
| It's a very common pattern for C memory allocation checking.
| A public example I know off the top of my head can be seen
| here: https://github.com/zedshaw/learn-c-the-hard-way-
| lectures/blo... (the implementation of the CHECK macro).
| That's in a C tutorial but I've implemented a version of that
| macro frequently.
|
| Let's say you need to dynamically allocate two buffers in a
| function and want to make sure they are freed at the end of
| your call. You can use this macro like so:
| int two_bufs(int n, int m) { int *buf_1 = NULL;
| int *buf_2 = NULL; buf_1 = calloc(n, sizeof(int));
| CHECK(buf_1); buf_2 = calloc(m, sizeof(int));
| CHECK(buf_2); // ... lots of cool things with
| buf_1 and buf_2 error: free(buf_1);
| free(buf_2); // Safe if null }
| jefftk wrote:
| You have "free(buf_2); // Safe if null", but if
| CHECK(buf_1) turns into a "goto error", won't buf_2 be
| uninitialized? And so can take on any value?
| davidwf wrote:
| You are correct, will edit. C is hard, writing C in the
| browser sans coffee is harder. :-)
| jefftk wrote:
| I do think this illustrates one of the issues with goto:
| normally the compiler would be able to warn that you were
| using buf2 potentially uninitialized, but I think you
| wouldn't get a warning in this case.
| Jtsummers wrote:
| One of the examples in the linked article showed the
| compiler emitting a warning when a variable wasn't
| initialized because the goto skipped past that line, it's
| in 31.7. I don't know what compilers will or will not
| give you that warning, but at least the one used for the
| article does. So it ought to catch the problem with the
| initial version of the example above as well.
| lilyball wrote:
| The Clang Static Analyzer could probably find this, if
| the compiler itself doesn't notice.
| NtGuy25 wrote:
| If you have a dispatcher or an infinite state machine, say an
| emulator. Or even just anything in general with cleanup, you
| use goto to "goto ERRORCLEANUP". So you have one exit
| condition to clean up. This is recommended in CERT c.
| adrian_b wrote:
| Few programmers use goto, because they have been
| indoctrinated against it and they have no understanding about
| when goto is bad and when goto is good, but there are many
| cases where using goto is better (i.e. more clear and/or
| faster) than what most programmers write now in such
| occasions.
|
| In some of those cases there are alternatives that could be
| better than goto, but they cannot be used because they need
| features that are missing from the programming languages
| popular now.
|
| One example is state machines. Many programmers when having
| to implement a state machine use a state variable, e.g. an
| enumeration and a big "switch" or "case" structure, depending
| on the language, with a branch for each state.
|
| This kind of implementation follows the method taught now to
| avoid goto's by adding unnecessary variables that are used to
| store some state when some condition is detected with the
| purpose to make a jump later with an extra if or switch
| instead of doing a jump with a goto immediately when the
| condition has been detected.
|
| This method of eliminating goto's is pretty stupid, because
| it not only adds useless variables but it also doubles the
| number of conditional branches, because each
|
| if (condition) goto label;
|
| is replaced by:
|
| if (condition) variable = value;
|
| ...
|
| if (variable == value) {...} or switch (variable) {...}
|
| Besides the accesses to non-cached memory, the conditional
| branches are the main causes of low performance, so doubling
| the number of conditional branches can result in much worse
| performance.
|
| When the second conditional branch is a "switch", that is
| even worse, because it is typically implemented as an indexed
| jump, which will be frequently mispredicted.
|
| Instead of using a state variable and a "switch" with
| branches for states, the right implementation is to have
| sequences of statements for the states, with the first
| statement in each sequence labeled with the name of the
| state.
|
| During each state, when the condition for the transition to
| another state is detected, a goto is executed, with the name
| of the next state.
|
| Anyone who believes that this is less clear or more difficult
| to read than the variant with state variables is delusional,
| because in the variant with goto's you see immediately the
| effect of a transition condition, while in the variant with
| state variables you just see some value being stored and you
| do not know its purpose until possibly much later when you
| discover that it is tested for a second conditional branch.
| Even after you see this use, you are not certain that this is
| the only place where the variable is used, it might also have
| other uses and you might need extra time until you are
| certain about what the program really does.
|
| A more elegant solution for state machines is possible only
| in languages where tail call optimization is guaranteed.
|
| In such languages each state may be implemented as a separate
| function and the goto's may be replaced with function
| invocations in final positions, which are transformed by the
| compiler into jumps.
|
| While not so many programmers need to write state machines,
| there are much more frequent uses where goto is superior,
| e.g. for error management in cases where exceptions are
| inappropriate, but describing when, why and how would be
| long.
|
| In general all the problems for which goto was considered
| harmful are not solved by eliminating goto but by restricting
| its behavior.
|
| There was a series of research articles by various authors,
| published between 1970 and 1975, whose conclusion was that
| the right kind of goto should be allowed to make only forward
| jumps and that the labels must have a scope restricted to the
| block in which they are declared. Therefore a goto should be
| able only to exit from a block but not to enter a block.
|
| An example of a programming language that had this restricted
| form of goto was the language Mesa, from Xerox.
|
| In C however, it would not be possible to restrict goto only
| to forward jumps without simultaneously adding guaranteed
| tail call optimization.
| InfiniteRand wrote:
| It's good for a simplified version of exception handling for
| C (granted C does have setjmp/longjmp but that's more like
| the complicated version of exception handling)
|
| If you hit an error condition in deeply nested logic and need
| to go to common error handling code or go to a recovery
| point, then goto makes sense.
|
| Often times in these scenarios simplifying the logic is also
| an option, but that isn't always the case, especially when
| dealing with external interfaces that have a lot of potential
| error conditions that cannot be dealt with easily from the
| immediate caller (I'm thinking primarily of system
| interfaces, but also potentially library interfaces).
|
| From what I've seen this is the only really good scenario for
| goto and I think the examples in the article more or less
| fall into this category of escaping from deeply nested error
| cases.
| brundolf wrote:
| Makes me realize that the way some people use caught-
| exceptions is actually just reaching for goto in a language
| that doesn't have it
| jjtheblunt wrote:
| state machines a la finite automata, which (for examples) are
| generated by compiler parser generators like yacc ?
| jjav wrote:
| Think of everythin which in java you would put in the catch
| and finally blocks. Those are nearly always most cleanly
| handled in C code by doing a goto to the cleanup/return
| section at the end of the function.
|
| You could work around this via tons of deeply nested blocks,
| but that makes the code very unreadable. The goto cleanup
| pattern makes the code intuitive and very readable.
|
| I still have a printout somewhere of an application from the
| 80s, maybe a dozen pages in small font, every few lines it
| jumps via goto to somewhere else, depending on state (global
| variables, of course). It was a serious nightmare to figure
| out where the code is going. That was the historical context
| of "goto considered harmful". It sure was! Code like that
| doesn't exist today, the lessons have been learned. It had
| nothing to do with clean jump to a cleanup section (which is
| what a finally{} block is, after all).
| mumblemumble wrote:
| What's wrong with the examples in the article?
| blntechie wrote:
| My first job was writing VB.NET code and it was hammered to us
| not to use GOTO. Once for a critical production bug, using GOTO
| was the quickest solution to fix and I used it with a comment
| to not fire me. And that code lived on for years.
| ehutch79 wrote:
| Sometimes a little bit of poison can make a great medicine.
|
| Goes both ways though.
| CJefferson wrote:
| I agree with you completely.
|
| I once made a poster (I wish I'd kept it) where I was tracing
| out the behaviour of a single mega function with around 50
| labels in it, and gotos all over the place.
|
| I worked on a reimplementation, slowly picking apart the
| function into subfunctions, while loops, recursive function
| calls, etc. Took me about 2 weeks.
| munificent wrote:
| That must have been so satisfying once it was done.
| prox wrote:
| You maybe the right person to ask... Aren't function /
| functioncallers a form of goto?
| CJefferson wrote:
| Technically yes, all code eventually compiles to goto, or
| goto equivalents (simplifying slightly)
|
| However, the idea behind "goto considered harmful" is
| almost all code can be written with function, loops and
| if/else statements and get just as efficient, and much,
| much easier to understand.
| continuational wrote:
| That error handling is extremely error prone. It may be the only
| option you have in C, but that doesn't make it any better.
| drodil wrote:
| The most amazing goto is:
|
| void main() { goto http; printf("Hello world!");
| http://www.hello-world.com }
| sltkr wrote:
| This won't actually compile, even if you format it correctly,
| because a label must be followed by a statement.
| panax wrote:
| When you are forced to follow stupid rules like having only one
| exit point in a function then goto can be used a lot in place of
| that, but what are we supposed to do if you are not allowed to
| both use goto or have multiple returns in a function (in C) ?
| zarzavat wrote:
| > what are we supposed to do if you are not allowed to both use
| goto or have multiple returns in a function (in C)
|
| Move jobs.
|
| But seriously this is a good point. goto is practically
| necessary to manage memory in C. Having only one return point
| is justified in functions that manage heap memory. Standard C
| has no RAII, defer, finally, or any other alternative but goto.
| me_me_me wrote:
| The only acceptable use-case for goto I came across was for error
| handling and cleanup.
|
| If there are multiple points of code failure that all require
| same cleanup procedure then one section at the bottom is
| acceptable and can actually keep the code cleaner.
|
| Anything else looks like bad design that ends up producing hard
| to follow code.
| mikevm wrote:
| Yes, this is pretty much how it is being used in C code. The
| other acceptable use-case is breaking out of nested loops.
|
| There's also `setjmp()`/`longjmp()` but that's even more rarely
| used.
| einpoklum wrote:
| longjmp() is the C-language version of going-to labels in
| other functions.
|
| Its basic "use" is not having to unwind the stack; but
| moreover, it lets you avoid cleanup and resource de-
| allocation. Usually it doesn't make sense to do that (you get
| memory leaks), but if, say, your memory allocations happen in
| some kind of arena - you can just leave it in any junk state
| and then proceed to clear it altogether after your setjmp()
| instruction.
| SAI_Peregrinus wrote:
| The most common use of setjmp/longjmp I've seen (in
| embedded development) is to have coroutines in C. The
| original use AFAIK was usually to implement exceptions of
| some variety. Neither use is that common these days.
| zelos wrote:
| I remember working with two C teams where one did:
| rc = do_something(); if (!rc) { rc =
| do_something_else(); } ... do_cleanup();
|
| and the other did: rc = do_something();
| if (rc) goto xout; rc = do_something_else();
| ... xout: do_cleanup();
|
| I think the goto was clearer and harder to get wrong?
| m4r35n357 wrote:
| You can use static local variables with goto to implement yield
| semantics in c, look it up!
| beej71 wrote:
| Oh good, I thought! Maybe there are some ideas here I could add
| to the book! :)
| 0xTJ wrote:
| Blanket rules like "never use `goto`" are generally not great. A
| big part of the job of a software developer is knowing when to
| use what bits of a language.
|
| Sometimes code with `goto` is simply easier to understand. You
| have to be careful, but dismissing it completely is throwing out
| a tool.
| TheCapn wrote:
| It's a rule given to those still learning the craft. Some
| people just don't realize that once you gain a level of
| expertise you _should_ question the rules you were given and
| make educated decisions on whether those rules were just guard
| rails to help you learn or actual constraints put in for a real
| purpose.
|
| When I was early on in code I abused the GOTO, once I learned
| it was taboo, I was forced to learn how to structure
| functions/statements in a way that flowed smoothly and created
| readability in the code that wasn't there before. Now that I
| understand the pitfalls, I use goto primarily in the "Bailing
| Out" form described int he link. To try and force the code to
| do what it does without the goto would likely create a bigger
| mess as I'd just be nesting statements beyond ever growing if-
| else branches designed to kick out when failure conditions
| occurred.
| mikevm wrote:
| People who give blanket rules such as never to use `goto`
| clearly do not understand where this idea comes from or why it
| should not be used. Many years ago in my undergrad I submitted
| a programming assignment in C which used `goto` for cleanup, as
| is customarily done in the Linux Kernel and other C software
| and I had points taken off for using `goto`. Heh.
| SAI_Peregrinus wrote:
| Should've used setjmp/longjmp to do the same thing. Make it
| even harder to reason about.
| munificent wrote:
| _> People who give blanket rules such as never to use `goto`
| clearly do not understand where this idea comes from or why
| it should not be used._
|
| The idea comes from Dijkstra and he sure as hell did consider
| it a blanket rule: "I became convinced that the go to
| statement should be abolished from all 'higher level'
| programming languages (i.e. everything except, perhaps,
| machine code)."
| bgorman wrote:
| C is portable machine code :)
| munificent wrote:
| Not in Dijkstra's time it wasn't.
| krapp wrote:
| On any modern system it really isn't[0,1].
|
| [0]https://news.ycombinator.com/item?id=16967675
|
| [1]https://queue.acm.org/detail.cfm?id=3212479
| atq2119 wrote:
| A lot of software engineering techniques are more about
| managing mediocrity than fostering excellence. They go
| overboard with the prescriptions. This may well produce better
| results when you have an army of mediocre developers.
|
| It tends to frustrate excellent developers though, so it's a
| tricky weapon to wield.
|
| Of course, it also tends to frustrate those who only think
| they're excellent.
| jll29 wrote:
| I like the presentation of the three nexted loops.
|
| Maybe "break;" should become a shortform for a generalized
| "break(1);" (= break one level) if we want to avoid using goto.
|
| Perhaps this can be implemented by combining a macro with
| setjmmp() and longjmp() from the standard C library, without
| requiring a compiler change.
| wruza wrote:
| Yep, some goto uses were ostracized without ever providing the
| alternative. In other languages breaking numerous loops is done
| by naming the one you want to control: outer:
| for () { for () { for () { break
| outer;
| OskarS wrote:
| This is much more confusing to me than just using a goto.
| From your example, it's not immediately clear at all to me if
| you're breaking out of outer, or if you're breaking TO outer.
| You can figure it out with this simple example, but it's way
| more confusing than it needs to be. And importantly: way more
| confusing than just using goto.
|
| Given that break and continue are just gussied up gotos
| anyway, just put the label where you wanna go and goto it. I
| think it's one of the few perfectly valid uses goto.
| Jtsummers wrote:
| There is no confusion if you're already familiar with the
| anonymous break: // find in a loop
| for (a : array) { if (a == target) {
| print("Found it"); break; } }
|
| This breaks the loop. A named break is no different except
| that it names the loop that will be broken out of:
| find: for (a : array) { if (a == target) {
| print("Found it"); break find; } }
|
| Exactly the same as the previous, but we've named the loop
| for some reason. If you can understand this, then the case
| of named breaks (or named continues) with multiple nested
| loops are comprehensible with some effort spent writing or
| reading illustrative examples like the above. Why would it
| do anything else? A break is a break, it terminates the
| loop. To re-enter that loop would be a continue, not a
| break.
|
| > Given that break and continue are just gussied up gotos
| anyway, just put the label where you wanna go and goto it.
| I think it's one of the few perfectly valid uses goto.
|
| The reason not to do this is that goto's can (as
| illustrated in the submitted article) lead to some
| erroneous behavior that is _harder or impossible_ to
| achieve with more structured equivalents (like named breaks
| and continues). Like, your goto can jump past variable
| initializations and get you undefined behavior. Using the
| structured equivalent of: for (a : array) {
| if (a == target) { print ("Found it");
| goto found_it; } } found_it: ...
|
| Removes those potential errors from the system. That, of
| course, doesn't mean that goto should be forbidden entirely
| (plenty of examples in this discussion of where it's
| useful), or that something like the preceding wouldn't be
| useful in some circumstance. But if we follow the idea that
| you present ("break and continue are just gussied up gotos
| anyway") to its conclusion, we'd be back to the
| unstructured code that was Fortran and its contemporaries,
| because why should we have if, if-else, while, for,
| function calls, etc. when they're _all_ just gussied up
| goto anyways?
| rightbyte wrote:
| The label should be at the end of the for block for no
| confusion. It just looks like the whole thing will be run
| again.
| Jtsummers wrote:
| If you expect break to go to the start of a loop, then
| you've confused it with continue.
| jkcxn wrote:
| Isn't it interesting that all of the examples in the original
| article can be replaced with a labeled break of arbitrary
| blocks? Even replacing the continue statement. Solving the
| problems of goto and keeping the behaviour. Not sure why more
| languages don't have this
| beej71 wrote:
| The next version of C is likely going to have `break break` to
| break two levels. I'd have preferred `break label` or no
| change, but there we are.
| buescher wrote:
| For those that haven't seen Knuth's "Structured Programming with
| go to Statements" before: https://pic.plover.com/knuth-GOTO.pdf
|
| Enjoy!
| tyingq wrote:
| Perl has goto, but it also supports labels you can apply to
| loops, and call for redo, next and last. Where "next" is like
| "continue" and "last" is like "break".
| jugg1es wrote:
| The utility of goto is one of those things I've always understood
| but been too embarrassed to say out loud.
| t43562 wrote:
| I find that using goto for cleanups makes many functions much
| much clearer and I think it cuts bugs.
|
| Rather than writing the cleanup that is appropriate for each
| particular exit point you write one big cleanup at the end of the
| function that deals with everything.
|
| It adds some potentially unnecessary if statements (to see if
| some pointer was allocated or not before trying to free it or to
| see if some file handle was opened before closing it). To me it
| has always seemed well worth it - shortening the code and greatly
| simplifying the chains of error-handling if statements.
| marcodiego wrote:
| Actually "GOTO Considered Harmful" should be considered harmful.
| But goto should but not used by beginners, or else they get bad
| habits. Also, people should consider that there is a proposal for
| "break break" in C2X: http://www.open-
| std.org/jtc1/sc22/wg14/www/docs/n2859.pdf
| GuB-42 wrote:
| I think we must distinguish between forward and backwards goto.
| And whether or not you are entering blocks.
|
| If you are not entering blocks, forward goto is usually fine, and
| for me, there is no good reason to think is is worse than break,
| continue and return. In fact, I would more readily ban mid-
| function return than this kind of goto.
|
| Backwards goto is when you get spaghetti code, there is some use
| for it, but it is very easy to make a mess.
|
| Entering blocks is particularly bad because it breaks both flow
| and initialization, I think if there is one kind of goto that
| should be considered harmful, that's the one, these can even
| break compilers. But as always, if there is a good reason to do
| it, why not, but you need a lot of convincing.
| garenp wrote:
| This is generally my sentiment as well. The reason to avoid
| using goto is if it makes following the flow of execution for
| us humans difficult, which typically happens when jumping
| backwards.
|
| Using goto judiciously to more easily escape a deeply nested
| block of code and/or jump to a cleanup section is not hard to
| follow.
| billpg wrote:
| My favorite use of goto is with C#'s "yield return".
|
| The compiler converts a function with a yield command into a
| label. To resume a function from where it left off, the compiler
| adds code to test an added resumption-point variable and to goto
| the label for each resumption point.
| heavenlyblue wrote:
| All code that ends up being written as assembly will become a
| command (an opcode or a set of opcodes) and a label (the
| opcode's address).
| fjfaase wrote:
| I have used the goto statement to implement enumerators classes
| in C++ using a state member and a switch statement with goto
| statements at the start of the method. Of course, also all
| local (loop) variables need to be replaced by (private)
| members. At each 'yield' location, you set the state variable,
| have a return statement, followed by a label.
|
| It is also possible to do this in C with functions and structs.
| See for example: https://www.iwriteiam.nl/Ha_cmt.html
| woliveirajr wrote:
| At StackOverflow you're downvoted heavily when you just typ got
| (don't event have time for the last "o").
|
| Then Apple comes with the glorious "goto fail" and shows that
| real companies use goto, and the problem wasn't "goto" itself.
| dleslie wrote:
| It seemed odd to me that their fail context didn't set 'err',
| but assumed it would be set by the origin of the jump. At
| least, I would have initialized 'err' to a failure value, so
| that any mistake that led it not to be set would result in a
| valid error state.
| Wildgoose wrote:
| I remember getting "sniffy" comments for using a GOTO in my final
| year project, (which was written in Pascal).
|
| Basically, it was used to terminate the program without having to
| thread a return-value through very heavily nested code.
|
| I haven't needed to use a GOTO since. But almost 35 years later,
| I still maintain it was the correct decision at the time: a
| single GOTO statement was the clearest and most efficient choice.
| bsenftner wrote:
| Many C/C++ UI frameworks require allocating component parts,
| linking them together to create an operating widget, and then
| attaching callbacks and whatnot... The code creating a
| dialog/window/toolbar with multiple such widgets is a perfect
| case for using goto to implement a single code block of unwinding
| of the complex construction of that dialog/window/toolbar.
| Multiple times I've had long winded debates on why complex
| dynamic memory structure unrolling is safest as a single code
| block. The subtle win is the unexpected MASSIVE code shrinkage
| that occurs when a UI's codebase adopts goto for dynamic
| structure unrolling. I've seen applications that dragged become
| nimble if not perky after the code reduction from removing 2/3rds
| of the code that was nothing but repeats of partial structure
| unrolling due to an error at one spot, and a similar one a few
| lines down with a complete duplicate of the upper unrolling and a
| bit more added, and then another duplicate a new lines further,
| and again and again and again...
| ahartmetz wrote:
| Maybe so in C, but in C++, stack-allocated container classes,
| smart pointers, scope guards(!), and (with Qt) the parent-child
| system can handle almost all cases. Binary size usually
| increases but lines of code are less or equal to a C-style
| solution.
|
| It's called RAII, but the main point is really that destructors
| are automatically called on scope exit.
| DeathArrow wrote:
| I learned Basic in the 90s and there was no for, no while, no
| functions or procedures. Just ifs and goto. Its hard to structure
| a program just with goto and it's even harder to read it and
| understand the flow.
| [deleted]
| einpoklum wrote:
| Can't say that I'm enthusiastic about goto advice, but - for a
| second there, I thought the domain was bejeez.us :-P
| Grazester wrote:
| When bit banging on a slower microcontroller I have found goto
| has it's uses.
| kazinator wrote:
| Goto is a useful precursor for writing a tail recursive solution.
|
| For instance, say we have a state machine for recognizing that
| the last four button presses were 1234. int
| keypad() // returns 1 if correct key is entered, otherwise loops
| forever { keypad: switch (getkey())
| { case 1: goto got_1;
| default: goto keypad; }
| got_1: switch (getkey()) { case 2:
| goto got_2; case 1: goto got_1;
| default: goto keypad; }
| got_2: switch (getkey()) { case 3:
| goto got_3; case 1: goto got_1;
| default: goto keypad; }
| got_3: switch (getkey()) { case 4:
| return 1; case 1: goto got_1;
| default: goto keypad; } }
|
| Now, refactor the same structure into tail calls. Now it's
| suddenly beyond reproach. int got_1();
| int got_2(); int got_3(); int keypad()
| { switch (getkey()) { case 1:
| return got_1(); default: return keypad();
| } } int got_1() { switch
| (getkey()) { case 1: return got_1();
| case 2: return got_2(); default:
| return keypad(); } } int got_2()
| { switch (getkey()) { case 3:
| return got_3(); case 1: return got_1();
| default: return keypad(); } }
| int got_3() { switch (getkey()) { case
| 4: return 1; case 1: return
| got_1(); default: return keypad();
| } }
|
| You can refactor any flow control graph based on goto, with local
| variables, into a network of tail calling functions. The tail
| call graph is isomorphic to the goto: it's just as complex and
| hard to understand. Yet it has the virtue of being beyond the
| reproach of computer science academia.
| JKCalhoun wrote:
| > Multi-level Cleanup
|
| Used all the time a decade or so ago when writing code that made
| heavy use of Apple's CoreFoundation. CF _could_ return nil
| (failure) for many operations, and memory management at that time
| was left up to the programmer.
|
| // Not real code, approximated from recollection
|
| bool createNestedCFThing () { CFDictionary
| *thing = nil; CFArray *arrayObj = nil; CFString
| *string1 = nil; CFString *string0 = nil;
| string0 = CFCreateString ("Hello"); if (string0 == nil) {
| goto bail; } string1 = CFCreateString
| ("World"); if (string1 == nil) { goto bail;
| } arrayObj = CFCreateArray (); if (arrayObj ==
| nil) { goto bail; } if
| (CFArrayAddObject (arrayObj, string0) == false) { goto
| bail; } if (CFArrayAddObject (arrayObj,
| string1) == false) { goto bail; }
| thing = CFCreateDictionary (); if (thing == nil) {
| goto bail; } if
| (!CFDictionaryInsertObjectWithKey (thing, arrayObj, "somekey")) {
| goto bail; }
|
| bail: if (arrayObj != nil) { CFRelease
| (arrayObj); } if (string1 != nil) {
| CFRelease (string1); } if (string0 != nil) {
| CFRelease (string0); } return thing; }
| mkipper wrote:
| This is the most common form of error handling used in the
| Linux kernel (at least the parts I look at). Although you'd
| normally have one error label for each allocation in reverse
| order, so you can jump to the cleanup step for the most
| recently allocated resource and pass through the other labels
| and clean them up as well. So in your example, it would be
| something like: bail_string1:
| CFRelease(string1); bail_string0:
| CFRelease(string0); bail_array:
| CFRelease(arrayObj); no_bail: return thing;
| vlovich123 wrote:
| Manual memory management is bad but you can't escape it in C so
| `goto` is mildly useful there for that purpose. In all other
| languages you get better ergonomics.
|
| * In Rust you wouldn't need any of that boilerplate because
| lifetime is automatically managed (& Rust has Objc bindings).
|
| * If you turn on ARC & use the NS types you don't need this
| (they map to the same thing with no overhead for these
| primitives, so there's no good reason not to do this unless you
| are just being masochistic).
|
| * In ObjC++ you should still use ARC. But if you felt
| masochistic, you could do: auto string0 =
| unique_ptr<CFString, CFRelease>(CFCreateString("Hello"));
| auto string1 = unique_ptr<CFString,
| CFRelease>(CFCreateString("World")); auto arrayObj =
| unique_ptr<CFArray,
| CFRelease>(CFCreateMutableArray(kCFAllocatorDefault, 2,
| kCFTypeArrayCallBacks, kCFTypeDictionaryKeyCallBacks, ));
| auto thing = unique_ptr<CFDictionary,
| CFRelease>(CFCreateMutableDictionary(kCFAllocatorDefault, 1,
| kCFTypeDictionaryKeyCallBacks,
| kCFTypeDictionaryValueCallBacks)); if (!arrayObj ||
| !string0 || !string1 || !thing) { return nil;
| } if (!CFArrayAppendValue(arrayObj.get(),
| string0.get()) || !CFArrayAppendValue(arrayObj.get(),
| string1.get())) { return nil; } if
| (!CFDictionaryInsertObjectWithKey(thing.get(), arrayObj.get(),
| "somekey")) { return nil; } return
| thing.release()
|
| In C++ you could also use scope guards if you didn't like the
| `unique_ptr` stuff (not part of the stdlib yet, but it's not
| hard to roll your own).
|
| The point I'm trying to make is that there are much cleaner
| ways of obtaining that result in whatever language you choose.
| If you do that, maybe you too won't be responsible for a goto
| fail security flaw [1].
|
| EDIT: BTW. This advise isn't out of the blue. This was 100% a
| strong culture even within Apple 5 years ago. If you're
| concerned about performance of ARC, consider that Apple
| dogfoods it internally for nearly every part of the OS. The
| only pieces it's not used are for large historical codebases
| where the migration cost is a factor. Performance of ARC vs
| manual just isn't something anyone looks at. For the historical
| codebases usually one would use the ObjC++ approach instead.
|
| [1] https://nakedsecurity.sophos.com/2014/02/24/anatomy-of-a-
| got...
| lmilcin wrote:
| This is my favorite use of goto. Not only it makes it more
| readable and easy to ensure the cleanup is only executed, it
| results in efficient binary representation which is important
| for some of the embedded stuff I am doing.
| N0tAThr0wAway wrote:
| Goto is like a tool in your tool shed that may only be used once
| a year or less. It has an extremely narrow use-case, can be
| dangerous is used improperly, and may require more experience and
| training versus using your run of the mill pipe wrench.
|
| I always hated the platitudes that people use when talking about
| gotos. Yes it definitely can be dangerous, yes it's probably best
| to refactor a specific area of your codebase to not use them, but
| every once in a while, it's the right tool for the right job.
| ravenstine wrote:
| Goto probably doesn't have much of a place in structured
| programming, but I think there are potential applications for it
| in more finite domains.
|
| Though I never finished it, years ago I was working on a language
| specifically designed for voice activated devices, interactive
| fiction games, and so on. I wasn't happy writing Alexa skills
| using languages like JavaScript because such languages have more
| features and complexity than are ever used for a voice interface.
| So I worked on a lexer-parser for a language I invented that was
| very minimal.
|
| These were the features of the language:
|
| - No syntactical scoping. You have to be explicit about the "data
| set" you are using.
|
| - Close to being more like an "assembly" language, every line
| being a command with parameters, but is human readable and
| declarative, and supports expressions.
|
| - Control flow is done not only through the equivalent of a
| "goto", but something like "goto chapter" and "goto section".
|
| - There is no syntactical concept of loops. Loops are effectively
| performed using gotos and conditions.
|
| - Lines beginning with whitespace are interpreted as text or
| speech to be rendered.
|
| For the purpose of what I wanted to do, this allowed me to focus
| more on the user experience and less on fitting my ideas into
| languages designed to support algorithms, OOP, and so on. The
| simplicity of using gotos meant less conceptual overhead;
| everything could be done with just if-else, goto, math operators,
| and negation.
|
| I imagine goto may also be a good way to introduce young kids to
| the concept of programming, though I suppose that could backfire
| if they get too used to writing unstructured programs.
| wruza wrote:
| Most programmers and I believe even most teachers see technology
| not as an application, but as some sort of a blind religion.
| Shallow understanding only adds to this problem. I've seen enough
| of them saying "what, goto?" in disgust, but almost no one could
| explain why, apart from saying that it is a taboo and maybe
| remembering that it was in some book.
|
| They need it, and it's really good that it was "goto". If it
| wasn't, they would jump on another, more useful bogeyman, like
| eval.
|
| And for those interested, the specifical use of goto that was
| _considered harmful_ is its use in place of "for" and
| "[do-]while" flow control statements and explicit subroutines.
| Literally: loop: ...; if (!cond) goto loop;
| a = 42; b = 13; goto sub; ... sub: ...
|
| It was used as such by inertia, because these were times before
| invention/adoption of the structured code.
|
| While you may not want to goto into inner scopes for obvious
| reasons, leaving two loops or goto-ing to a function cleanup
| section is perfectly okay and is much clearer than breaking the
| outer loop with a boolean or building ladders of ifs.
| bigbillheck wrote:
| Structured programming has won so thoroughly and become so
| pervasive that it's hard nowadays to understand the context in
| which Dijkstra was writing.
|
| 'Don't use goto' doesn't mean 'never ever use a goto under any
| circumstance', it means 'consider using this thing called a
| "while" loop instead'.
| munificent wrote:
| _> 'Don't use goto' doesn't mean 'never ever use a goto under
| any circumstance',_
|
| This is simply not true. When Dijkstra said goto should be
| considered harmful, he literally meant that _programming
| languages should not suppport it as a feature at all_.
| ladyattis wrote:
| I think the issue is only relevant when considering the years
| when programming languages began to have more nuanced
| instructions. For Assembly, goto is an essential construct and
| you'll learn how to use it reliably as jump conditions. For C,
| it's a great way to get out of nested code that you couldn't
| avoid for the given problem. For more high level languages,
| continue and break are the 'goto' statements and they're used all
| over but they have built in restrictions which make them easy to
| use. Goto is just hard to master in use for some of us and it's
| okay to restrict ourselves to limited uses of it. I just don't
| know why it's become a mantra to say, "do not ever use goto or
| labels or jump conditions what so ever." It just seems odd to me.
| dec0dedab0de wrote:
| _For more high level languages, continue and break are the
| 'goto' statements..._
|
| I have never liked continue or break. It always felt like I
| messed something up if I found myself needing them.
| vadfa wrote:
| Like any other tool in the arsenal of a programming language:
| when it best suits the task.
| hyperman1 wrote:
| There are a few situations like this, where a bad practice is in
| a specific case good enough: Using goto to bail out multiple
| levels, using SHA-1 as a non-secure hash, ...
|
| The problem is these things waste social bandwith: Someone
| reading your code for maintenance or code review will first
| declare the code bad (GOTO is ugly! SHA-1 is insecure!), then you
| have to signal to them that it's actually OK in this case, then
| they have to convince themselves that actually it is OK.
|
| Hence I only do these things if they are overwhelmingly good.
| They have not only to be worth it to be written that way, but
| also be worth it to do the repeated discussion that they are
| worth it every few months. They deserve a huge comment,
| containing benchmarks or proof of security or whatever. If the
| construction is only slightly better, I go with the slightly
| worse construction.
| simion314 wrote:
| You can link them to the manual, good languages documentation
| explain clear and when examples when is better to use GOTO. If
| there is an asshole in the team the official documentation
| would calm them down after they will be probably shocked that
| their cool language implemented this "evil" feature.
| SAI_Peregrinus wrote:
| > using SHA-1 as a non-secure hash,
|
| That I actually partly disagree with. There are much, MUCH
| better performing (faster, less memory use, etc) hashes than
| SHA1 if you don't need security. If you don't need security,
| _and_ you don 't need performance, and you don't have anything
| else available in libraries, then SHA1 is fine. But that's a
| pretty rare situation. SHA1 might not be a sign of insecurity,
| but it's often a sign of poorly thought out design.
| adrian_b wrote:
| On modern Intel/AMD and ARM CPUs, which have SHA-1
| instructions, SHA-1 is very fast, faster than many apparently
| simpler hashes.
|
| There are no other so fast hashes with an output of at least
| 128 bits that you can find in available libraries and use
| immediately in your code.
|
| The only alternative that is faster and long enough is to use
| an 128-bit polynomial hash like Poly1305 or the one used
| inside AES-GCM. Such polynomial hashes are available in
| various cryptographic libraries, but they are not packaged in
| a way that would allow them to be used directly in a hashing
| application, so you might have to extract the code from the
| library and add an interface to it, to make it usable.
|
| Another alternative that has appeared recently is BLAKE3.
| BLAKE3 can be much faster than SHA-1, but only when it is
| computed in parallel with a large number of cores. If you do
| not want your hashing task to entirely take over your
| computer, SHA-1 remains faster.
|
| There are many applications that need long hashes to make
| negligible the likelihood of a collision, e.g. for file
| deduplication. For such applications using SHA-1 is by far
| the least effort choice and there is no disadvantage in using
| it.
| hyperman1 wrote:
| I'm not going to contradict you. This was just one random
| example, based on the git/SHA-1 story. To stay in this
| example: If someone is claiming SHA-1 is faster for them, I'd
| expect to document what other algorithms they tested, what
| the numbers were, why better things were unavailable. A
| future maintainer can check if the decision still makes sense
| based on the comment. Hence the huge comment from the post
| above.
|
| The problem is, I have a few examples that make a lot more
| sense, but they can't be published here. They depend on local
| corporate circumstances and pretty obscure knowledge, and
| would be undecipherable for the HN crowd.
| leephillips wrote:
| Julia's approach is to not have GOTO as part of the language
| syntax, but to have @goto and @label macros. I feel that this
| strikes a good balance: it sort of discourages their routine
| use, but they're there if you really need them.
| skipants wrote:
| > The problem is these things waste social bandwith: Someone
| reading your code for maintenance or code review will first
| declare the code bad (GOTO is ugly! SHA-1 is insecure!), then
| you have to signal to them that it's actually OK in this case,
| then they have to convince themselves that actually it is OK.
|
| This is something that changes over time as the new convention
| gets reinforced. If you don't push back it will never change.
| In other words, the conventional way of doing things needs to
| be spread organically.
|
| A lot of conventions work that way. Since I'm in Web Dev, one
| example I can think of is the change from fat models to service
| objects in MVC. Fat models used to be seen as the way of doing
| things. But, now that many of us have encountered giant balls
| of mud caused by this pattern, we've adapted a new convention
| and moved on.
|
| If you can provide good uses of goto in the wild and push back
| against opposition the same thing will happen, in my opinion.
| Eventually the opposition goes away.
| userbinator wrote:
| Another good use of goto is to implement state machines --- "goto
| state_x" is clearer and simpler than the loop-switch that people
| often use.
|
| I can certainly say that over the years I've encountered code
| which was more difficult to understand and less efficient because
| either the author didn't want to use goto, or the language didn't
| have it. While it's true that you can always rewrite code to
| remove goto statements, it can definitely have a negative effect.
| beej71 wrote:
| Oh, that's an interesting use I hadn't considered!
| enriquto wrote:
| Coming from assembly language, I always found the anti-goto
| sentiment rather cute. A beautiful restriction, but ultimately
| arbitrary. Like writing poetry. Or those novels that do not ever
| use the letter "e". Why would an otherwise sane person write code
| with "rep" and without ever using "jmp"?
| samhw wrote:
| > Or those novels that do not ever use the letter "e".
|
| 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.
| recursive wrote:
| Also Gadsby by Ernest Wright
| jcranmer wrote:
| What is done is that people have taken the useful cases of
| goto, categorized them, and use renamed keywords for different
| cases. In many modern language, there are four keywords that
| all imply a general feature of goto: break (go to the end of
| the identified block), continue (go to the increment block of
| the identified loop), early return (go to the function cleanup
| code), and throw (go to something complicated). Indeed, with
| labeled break and labeled continue, you can actually produce
| almost any arbitrary control flow [1].
|
| 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.
| slaymaker1907 wrote:
| I would disagree with defer being better than goto. Some
| people prefer it, but I think in a lot of ways it makes your
| control flow harder to see than just a few cleanup blocks at
| the end plus goto.
| Jtsummers wrote:
| It's a tradeoff.
|
| _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.
| slaymaker1907 wrote:
| There is a huge difference in semantics with defer
| compared to RAII since defer is called at the end of the
| function while RAII is scope based. It's mostly the same
| except for if you want to use RAII in a loop.
| Jtsummers wrote:
| That's a difference based on the way Go implements defer,
| but not a principle _of_ defer. A lexically scoped based
| defer is conceivable.
|
| 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.
| rightbyte wrote:
| I guess Go's "defer" is from "rescue" in Alef (the Plan 9
| language). Kinda the same guys. But "rescue" was only
| executed on signals.
| naniwaduni wrote:
| Goto aversion, like single-entry/single-exit and everything
| anyone has ever said about 3rd-to-5th-generation programming
| languages, addresses a concern that hasn't been relevant in
| decades and that most modern programmers have no context for
| whatsoever.
|
| 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.
| IshKebab wrote:
| It's not at all like that. `goto` leads to hard to follow, bug-
| prone code.
|
| > 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.
| efaref wrote:
| Avoiding jmp in assembly _is_ a sensible thing to do. jmp may
| stall the pipeline if the branch predictor is wrong. Better
| to use cmov when you can.
|
| And if we're being silly, you only really need mov[1].
|
| [1]: https://github.com/xoreaxeaxeax/movfuscator
| IshKebab wrote:
| That's a completely separate architecture-specific micro-
| optimisation.
| mytailorisrich wrote:
| It's not arbitrary.
|
| 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.
| lelanthran wrote:
| > 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"
|
| Sure, but _constrained_ uses are fine, right? So why blanket-
| ban it?
| metarox wrote:
| MISRA doesn't ban it, that rule is only Advisory which
| pretty much means you can ignore it. Rules 15.2 and 15.3
| (Required) define how you can use goto if you decide to use
| 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
| lelanthran wrote:
| I'm familiar with MISRA (embedded dev), I only wanted to
| point out that the MISRA rules in no way justifies a
| blanket ban on gotos.
|
| The ban (in the case of C, anyway) is almost always the
| result of someone's completely subjective opinion that
| gotos are bad.
| mytailorisrich wrote:
| It's easier to 'ban' it and then to make exceptions on a
| case by case basis when not using _goto_ make things
| worse.
|
| 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.
| panax wrote:
| A customer put a blanket ban on goto because they decided
| to require MISRA 15.1 and want(need) to be compliant as
| well as having their own interpretation of MISRA. They
| also put a blanket ban on multiple returns (15.5).
|
| 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.
| metarox wrote:
| I fully concur on that last statement!
| buescher wrote:
| At least one of the versions of MISRA explicitly allows for
| the use of goto to break to cleanup code at the end of a
| function, if I remember correctly, and another is much more
| strict, but I don't have any copies of MISRA at hand at the
| moment.
| zabzonk wrote:
| Well, in C you very rarely need it, but in assembler
| (particularly the 8-bit Z80 and 6502 that I've written raft-
| loads of code for) you definitely do. Of course, something
| like a CALL is better, if you can manage it.
| sys_64738 wrote:
| It's mostly used for error bailing as nested if can be
| pretty dense to read and error prone.
| formerly_proven wrote:
| goto is the idiomatic way to emulate RAII in C. It's
| everywhere.
| zabzonk wrote:
| Please explain a little more with an example. Certainly
| not in my C code, though I a huge fan of RAII in C++.
| panax wrote:
| Here is an example with RAII and goto: https://wiki.sei.c
| mu.edu/confluence/display/c/MEM12-C.+Consi...
|
| 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...
| zabzonk wrote:
| Not many of us are kernel developers, or agree whole-
| heartedly with their practices.
| ahartmetz wrote:
| A function that allocates resources in several steps
| jumps, on error, to the teardown part at the end to
| deallocate (in reverse order of allocation) only the
| resources that were allocated. The others are skipped
| (jumped over). It's very popular in the Linux kernel.
| zabzonk wrote:
| > only the resources that were allocated
|
| 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.
| ahartmetz wrote:
| The information is in the instruction pointer. You jump
| to the place that deallocates the last resource that was
| allocated, followed by the second-to-last etc.
|
| 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.
| zabzonk wrote:
| > free(NULL) is not well-defined in C
|
| Oh yes it is:
| https://stackoverflow.com/questions/1938735/does-freeptr-
| whe...
| ahartmetz wrote:
| I stand corrected. So, these days, null checks before
| free() are just for compatibility with very old systems,
| or more likely "tradition". I wonder where I got my wrong
| information? I have even seen null checks before delete
| in C++ code recently written by C programmers :o
| wott wrote:
| When it is in the normal execution path, the test avoids
| a function call, so there's a little bit of performance
| gain.
|
| 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.
| maccard wrote:
| Files, sockets, mutexes are all examples of resources
| that aren't memory. Someone posted an example in this
| comment thread - [0].
|
| [0] https://wiki.sei.cmu.edu/confluence/display/c/MEM12-C
| .+Consi...
| zabzonk wrote:
| > Files, sockets, mutexes are all examples of resources
| that aren't memory
|
| I know - that's why I explicitly mentioned memory.
| scoutt wrote:
| I kind-of agree with parent, but I would define it
| "subjective" instead of "arbitrary".
| mytailorisrich wrote:
| Well, as per my previous comment it's not subjective,
| either.
|
| 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".
| scoutt wrote:
| > improve quality by making the code better structured
| and easier to follow
|
| "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.
| dnautics wrote:
| Rust is not proven to be bug free, it's proven to be
| memory-safe. Rusts compiler will not save you from bugs
| in business logic; if you can't write lucid rust and the
| gunning fog of reviewing your code makes it harder to
| spot a bug it might not be for you.
| mysterydip wrote:
| Isn't the key word in that statement "unconstrained"? Goto is
| a tool like any other that can be abused.
| mytailorisrich wrote:
| That's why there is this rule to constrain it!
|
| 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.
| enriquto wrote:
| I agree with you; and I keep a printed copy of the MISRA C
| document on my nightstand for serene reading before sleep
| (together with the JPL C rules). Yet this point 15.1 does not
| forbid _at all_ to use goto, just to use goto in an
| unconstrained way. Thus it is more a warning than a rule.
|
| 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.
| panax wrote:
| And yet we were recently forced to change a lot of code
| which did use goto in a constrained way(for error handling
| only), because someone decided to make 15.1 required
| instead of advisory and take 15.1 too literally (they also
| made 15.5 no multiple returns required as well) and some of
| the results were worse than if someone had used goto in an
| unconstrained way.
| mumblemumble wrote:
| > A beautiful restriction, but ultimately arbitrary. Like
| writing poetry.
|
| 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.
| jsmith45 wrote:
| Right. He was criticizing the version where one subroutine
| might jump into the middle of another subroutine.
|
| 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.
| pajko wrote:
| There's setjmp() and longjmp()to do that :)
| marcan_42 wrote:
| setjmp() and longjmp() only work upwards through the call
| stack. They're basically an awkward version of
| exceptions. You can't jump _into_ a subroutine that isn
| 't already executing with them.
| userbinator wrote:
| I started with Asm too and probably have much the same thoughts
| on goto --- HLLs provide if/else and loops to simplify code, so
| use them when it makes sense, but when the control flow is such
| that the simplest solution doesn't fit within the constraints
| of HLL flow structures, then goto makes perfect sense.
|
| 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.
| amelius wrote:
| Goto can, conceptually, really mess with the semantics of
| initialization and finalization of local variables.
| zabzonk wrote:
| Indeed, which is why C++ forbids (and the compiler checks)
| such jumps.
| da_big_ghey wrote:
| It is some good point that goto has many helpful uses, it is
| when people use it for working around when control flow should
| be re-factored instead that it becomes harmful. Like on many
| other things it is no issue in moderation, only in excess.
| throw_m239339 wrote:
| > Coming from assembly language, I always found the anti-goto
| sentiment rather cute. A beautiful restriction, but ultimately
| arbitrary. Like writing poetry. Or those novels that do not
| ever use the letter "e". Why would an otherwise sane person
| write code with "rep" and without ever using "jmp"?
|
| 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.
| msla wrote:
| > There is no need to use GOTO when a language has
| functions/procedures and exceptions.
|
| 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.
| mumblemumble wrote:
| One thing a C-like goto has over exceptions is that you can
| only jump around within a single lexical scope. That
| restriction is a big win for readability.
|
| 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.
| srott wrote:
| > There is no need to use GOTO when a language has
| functions/procedures and exceptions.
|
| ... and defer
| dragonwriter wrote:
| > and defer
|
| Which "defer"? The go panic/defer pair is, while structured
| differently, functionally equivalent to exceptions.
| 0xFACEFEED wrote:
| You use exceptions to clean up resources even when there
| are no errors? Why?
| dragonwriter wrote:
| > You use exceptions to clean up resources even when
| there are no errors?
|
| 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.
| Jtsummers wrote:
| You don't need to use exceptions for that, but a
| _finally_ block, something like this: 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.
| lmilcin wrote:
| > There is no need to use GOTO when a language has
| functions/procedures and exceptions.
|
| 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.
| Symmetry wrote:
| Likewise using local variables instead of global variables. The
| point of structured programming is that it's less flexible than
| gotos are. Decreasing the flexibility of programming constructs
| means that the reader has fewer possibilities to consider and
| hence makes the result easier to reason about.
|
| 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.
| heavenlyblue wrote:
| Even in assembly there's position-dependent and position-
| independent code.
| wruza wrote:
| And jumps relative to cs or ip, which serve both. PIC idea is
| not related to goto.
| kristopolous wrote:
| Bringing it more generally, it's a powerful tool that's simple
| to use.
|
| 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
| [deleted]
| ainar-g wrote:
| The site seems to be hugged to death, at least to me. Archive
| link:
| https://web.archive.org/web/20211021120233/https://beej.us/g....
| gwbas1c wrote:
| In the past 20 years I've used a goto a few times, and then
| refactored it out once I was able to look at the problem with a
| clear head. I remember that, every time, I replaced the goto by
| breaking up a large method into smaller methods, and then
| replacing the goto with either return statements or logic that
| would essentially return.
|
| Yes, a goto is part of our programmers' toolbox. But, now even I
| consider a "good goto" a sign that a larger method should be
| broken into smaller ones.
| recursive wrote:
| Often this is true, but those smaller methods might need to
| share so much local state that the solution is worse than the
| problem.
| Jtsummers wrote:
| This is where languages that permit nested functions come in
| handy. You can locally factor out new functions while
| preserving access to variables in the lexical scope without
| needing to pass them in as parameters or lift them into a
| higher level scope (object, class, global, file, etc.).
| recursive wrote:
| That's a good solution. The next problem is that many code
| patterns, such as state machines, if naively converted from
| `goto` to `call()` will consume a lot of unnecessary stack
| space. This might not be a problem with a language/compiler
| that supports tail call optimization.
| Jtsummers wrote:
| Right. Absent TCE and inline functions (the latter is now
| pretty much bog standard in every language in popular
| use, at least every compiled language), goto for state
| machines and similar uses can be much more efficient and
| also handle the concern of blowing up your stack. If you
| have TCE and inline functions, then mutual recursion is a
| perfectly efficient way to handle that kind of situation
| that is often (but not always) clearer than goto. And if
| you pair that with nested functions so that you can close
| over some common lexical scope, you eliminate the need to
| use global variables or to thread data through each
| function call (keeping your parameters to a minimum).
| gwbas1c wrote:
| Perhaps that's true when you're writing performance-
| critical code.
|
| For most code, readability trumps micro-optimizations.
| recursive wrote:
| In plenty of environments, there's a very limited number
| of calls you can make before the program fails entirely.
| For instance, in GW-BASIC, your namesake, the limit is
| less than 100 calls. There are plenty of real useful
| production langauges where your stack will run out before
| 1000 frames of depth.
|
| But on the other hand, it's quite reasonable to write a
| state machine which is expected to make [mb]illions of
| state transitions.
|
| Crash vs not-crash is not a micro-optimization.
| munificent wrote:
| I tend to find that to be itself a code smell. But it
| happens. When it does, I like using local functions that just
| close over the same state.
___________________________________________________________________
(page generated 2021-10-21 23:01 UTC)