[HN Gopher] GOTOphobia considered harmful in C
       ___________________________________________________________________
        
       GOTOphobia considered harmful in C
        
       Author : jandeboevrie
       Score  : 83 points
       Date   : 2023-02-26 08:15 UTC (14 hours ago)
        
 (HTM) web link (blog.joren.ga)
 (TXT) w3m dump (blog.joren.ga)
        
       | owisd wrote:
       | > On the other hand, today we have the very opposite situation:
       | programmers not using goto when it's appropriate and abusing
       | other constructs, what ironically makes code only less readable.
       | 
       | One I see all the time from beginners is creating a finite state
       | machine using one method per state and jumping between states by
       | calling the next state's method from within the current state's
       | method. Essentially just emulating goto with the added
       | disadvantage that you're pushing each new state onto the stack
       | until it overflows, so refactoring it using goto would constitute
       | an improvement.
       | 
       | The fact that beginners reinvent this pattern over and over
       | demonstrates it's easier for people unfamiliar with programming
       | to reason about a program that uses goto, which would explain why
       | it was so ubiquitous in the early days of computing and given its
       | shallower learning curve its usefulness as a teaching aid as a
       | first step before structured programming is being overlooked.
        
         | rmckayfleming wrote:
         | This is one of those problems that Tail Call Elimination
         | cleanly addresses. The "obvious" approach to writing an FSM
         | does exactly what you'd expect.
        
           | hgsgm wrote:
           | C does not guarantee tail call elimination.
           | 
           | But you can do the same with an event loop that cleanly
           | avoids goto.
        
             | IncRnd wrote:
             | Nowadays you can direct clang to require tail-call
             | elimination in C. [1] In gcc you can provide the
             | optimization flag, -foptimize-sibling-calls, which is
             | automatically selected at -O2, -O3, or -Os. [2]
             | 
             | [1] https://clang.llvm.org/docs/AttributeReference.html#mus
             | ttail
             | 
             | [2] https://gcc.gnu.org/onlinedocs/gcc/Optimize-
             | Options.html
        
         | tester756 wrote:
         | Where in the world beginners use state machines instead of
         | switch statement or if ladder?
         | 
         | this is genuine question, because I do believe that people do
         | not use enough state machines to model their systems.
        
       | jejones3141 wrote:
       | I agree that in C, the disciplined use of goto is the way to go,
       | but the "ok so far" version could be better written:
       | if (oksofar) {             oksofar = something_done =
       | do_something(bar);         }         if (oksofar) {
       | oksofar = stuff_inited = init_stuff(bar);         }
       | 
       | and so forth.
        
         | mytailorisrich wrote:
         | Or even oksofar = oksofar && do_something() to do away with the
         | cumbersome ifs.
        
       | kmeisthax wrote:
       | In RAII languages[0], you obviously don't need unrestricted
       | gotos.
       | 
       | However, I always find myself missing it when writing nested
       | loops. Labeled break and continue[1] ought to be considered
       | standard structure programming primitives. These are _restricted_
       | gotos and allowing them to break or continue a parent loop doesn
       | 't unrestrict them much. But it does significantly improve the
       | expressive power of your looping constructs.
       | 
       | [0] C++, Rust, Go, or anything else with automatic memory
       | management and destructors
       | 
       | [1] I've also heard of _numbered_ break /continue. Personally I
       | think this isn't good enough: what if I need to move loops
       | around? That will change the meaning of a `break 2`. With a
       | `break OUTER` the compiler will yell at me if I remove the outer
       | loop without changing all the code that breaks out of it to
       | target a different one.
        
         | jandrewrogers wrote:
         | Even with RAII in C++, goto is still useful for handling the
         | occasional error case where you need to reset/retry some
         | operation e.g. due to transient hardware issues that are not
         | unrecoverable errors. A common example that comes to mind
         | immediately is asynchronous disk reads where the data was
         | corrupted during transfer but may succeed if transparently
         | cleaned up and re-issued.
         | 
         | I use goto rarely. But there are times when anything else would
         | be inelegant, and in those instances I'll use it without
         | hesitation.
        
           | rightbyte wrote:
           | Gotos are quite nice for some state machines too. You
           | essentially store the state in the instruction pointer
           | instead of some helper variable.
        
         | quietbritishjim wrote:
         | > Labeled break and continue ...
         | 
         | I find that normally if I need nested break then it suffices to
         | refactor the target loop into a function and use return
         | instead.
         | 
         | I don't think I normally miss multilevel continue, but the same
         | strategy would work for that too. You'd just pull out the
         | target loop body rather than the whole loop.
         | 
         | If that doesn't work because you need to select too many
         | different break/continue levels (more than two) then maybe it's
         | time to review the complexity of the function anyway.
        
         | moregrist wrote:
         | This is one of those half-true claims that C++ aficionados
         | make, probably because they can play a bit fast and loose with
         | resource cleanup in their particular situations.
         | 
         | RAII simplifies some of the resource cleanup, but at a cost: if
         | the resource cleanup fails, there's essentially no way to
         | convey this.
         | 
         | So yes, you can write a destructor that tries to clean up your
         | resources regardless of how the code exits its current scope.
         | But if that cleanup encounters a problem, the destructor can,
         | at best, try to convey this indirectly, either by (commonly)
         | logging or (rarely) setting a variable to a value. It certainly
         | cannot throw another exception, or even directly manipulate the
         | return value of the function.
         | 
         | This is fine for some purposes, and completely unacceptable for
         | others. But it's not equivalent to the explicit cleanup and
         | error handling in C.
         | 
         | So some of us occasionally find ourselves in an RAII language
         | and using goto.
        
           | spacechild1 wrote:
           | > RAII simplifies some of the resource cleanup, but at a
           | cost: if the resource cleanup fails, there's essentially no
           | way to convey this.
           | 
           | RAII at least provides a decent default. After all, most
           | resource cleanup cannot fail, or you cannot do much other
           | than print an error. Now, _if_ you need to catch errors
           | during a particular cleanup, you can still do it manually,
           | but RAII lets you focus on these few cases.
        
           | dwattttt wrote:
           | If you need fallible cleanup, but also to try cleanup via
           | RAII, it isn't hard to have a "cleanup" method that signals
           | whether it succeeded, and an "already cleaned up" boolean
           | member the destructor checks.
        
           | vlovich123 wrote:
           | You can totally throw an exception from RAII code if you
           | declare the destructor noexcept(false).
           | 
           | Goto + RAII isn't generally compatible unless you structure
           | things very carefully
        
             | moregrist wrote:
             | You can, and your program will call std::terminate if
             | there's already an exception being processed. Not exactly
             | desirable if you're trying to write code that ensures
             | careful resource cleanup.
             | 
             | Also why it's widely regarded as _wrong_ to ever throw in a
             | destructor.
        
               | kentonv wrote:
               | IMO this is a design bug in C++. The authors couldn't
               | agree on what to do in the exception-during-unwind
               | scenario, so they chose the worst possible option: crash.
               | 
               | In most cases, an second exception raised while another
               | exception is already being thrown is merely a side-effect
               | of the first exception, and can probably safely be
               | ignored. If the idea of throwing away a secondary
               | exception makes you uncomfortable, then another possible
               | solution might have been to allow secondary exceptions to
               | be "attached" to the primary exception, like
               | `std::exception::secondary()` could return an array of
               | secondary exceptions that were caught. Obviously there's
               | some API design thought needed here but it's not an
               | unsolvable problem.
               | 
               | If we could just change C++ to work this way, then
               | throwing destructors would be no problem, it seems? So
               | this seems like a C++-specific problem, not fundamental
               | to RAII.
               | 
               | That said, there is another camp which argues that it
               | fundamentally doesn't make sense for teardown of
               | resources to raise errors. I don't think you're in this
               | camp, since you were arguing the opposite up-thread. I'm
               | not in that camp either.
        
               | bregma wrote:
               | Something like std::nested_exception [0]?
               | 
               | [0]
               | https://en.cppreference.com/w/cpp/error/nested_exception
        
               | kentonv wrote:
               | Sort of, but nested_exception covers a different
               | scenario. With nested_exception, the "attachment" is an
               | exception which _caused_ the exception it is attached to.
               | In the scenario I 'm talking about, the "attachment" is
               | an exception which was _caused by_ the exception it is
               | attached to.
               | 
               | Anyway, the key missing thing is not so much the
               | exception representation, but the ability to have custom
               | handling of what to do when an exception is thrown during
               | unwind. Today, it goes straight to std::terminate(). You
               | can customize the terminate handler, but it is required
               | to end the process.
        
               | bhawks wrote:
               | Even if the standard consolidated on one way or another
               | to pack up secondary exceptions (or discard them) how
               | likely is the calling code going to be able to handle and
               | recover from this case?
               | 
               | I am personally on team crash - I would rather my program
               | exited and restarts in a known state then being in some
               | weird and hard to replicate configuration.
        
         | kentonv wrote:
         | > In RAII languages[0], you obviously don't need unrestricted
         | gotos.
         | 
         | You don't need them for teardown, but they still make sense for
         | retries -- cases where a procedure needs to start over after
         | hitting certain branches, e.g. a transaction conflict. I think
         | `goto retry` is a lot more readable than wrapping the procedure
         | in `do { ... } while (false)` and using `continue` to retry.
         | 
         | `goto` works very nicely with RAII here in that it'll invoke
         | the destructors of any local variables that weren't declared
         | yet at the point being jumped back to.
        
         | jolux wrote:
         | Go doesn't have RAII or destructors, it has defer.
        
           | tedunangst wrote:
           | Which definitely does weird things in a loop.
        
             | Groxx wrote:
             | IIFEs trivially solve that.
             | 
             | tbh I see FAR more range-var-misuse with Go loops than
             | defers. I've seen over 100 range-var problems, and seen
             | lints catch many more (several thousand), but I've only
             | seen a loop defer issue once. When a defer is needed, it
             | seems like people both remember the issue better (`defer`
             | is a new construct for many, `for` is not and habits from
             | other languages can mislead them), and the code is complex
             | enough to justify a helper func, where a defer is trivially
             | correct.
        
           | nextaccountic wrote:
           | defer proposal for C made in 2021 https://www.open-
           | std.org/jtc1/sc22/wg14/www/docs/n2895.htm
           | 
           | ... why isn't this in C already?
        
             | jnwatson wrote:
             | In reality, it is already in every compiler that matters.
             | For some reason, they don't want to put it in the standard.
        
             | wruza wrote:
             | You must be new here. They'll consider adding it in late-
             | mid-'40s.
        
       | ipaddr wrote:
       | Drawbacks:
       | 
       | additional variables
       | 
       | Are we still chasing down the least variables possible in 2023?
        
         | userbinator wrote:
         | Yes, because they multiply state.
        
       | EGreg wrote:
       | Considering harmful is starting to be considered harmful, that's
       | what I am coming around to.
        
       | userbinator wrote:
       | The state machine example is definitely a very fitting use of
       | goto, but it reminds me of another thing that seems to have
       | become a rare skill but is very useful: flowcharting. Besides
       | making people comfortable with goto in general, it also helps
       | visualise control flow in ways that a lot of programmers these
       | days don't realise, and it's unfortunate that a lot of courses
       | seem to have omitted its teaching.
       | 
       | Also worth reading is "GOTO Considered Harmful Considered
       | Harmful": https://news.ycombinator.com/item?id=11056434
       | 
       |  _And here Microsoft provides us with lovely example of such
       | ridiculous nesting._
       | 
       | That's a very memorable example, but ultimately the true cause of
       | that monstrosity is a clearly stupid API design; this is the API
       | for a file picker, the recommended replacement for an existing
       | one that they wanted to deprecate. In the existing one, you fill
       | in a structure and call a single function with a pointer to it.
       | In its replacement, you need to call a dozen methods on an
       | object, and check for "possible" errors on each call, even if
       | probably 99% of them only do things like assign to a field in a
       | now-opaque structure and can never produce an error. Then the
       | example code must've been edited by someone with severe
       | gotophobia. (Not all MS code is like that --- they have plenty of
       | other example code that uses goto, e.g.:
       | https://github.com/microsoft/Windows-driver-samples/blob/mai... )
       | The existing API was even extensible, since it used a structure
       | with a size field that could differentiate between different
       | versions and extensions, but they didn't.
        
       | zxcvbn4038 wrote:
       | I always put a goto in all of my code just to mess with the "goto
       | considered harmful" people. Most people don't notice but every
       | once in a while I find someone who can't make it past the goto
       | and it puts a smile on my face. =)
        
       | raldi wrote:
       | The best analogy I ever heard on this is that using a goto is
       | like knocking a hole into a wall: it can be very useful, or even
       | essential, in some specific circumstances, but you should give it
       | a bit of thought. Also, it would be foolish to swear off ever
       | allowing either.
        
       | dcow wrote:
       | If you've ever actually written more than toy C, you'll know
       | gotos are essential for resource cleanup and error handling. Even
       | the famous goto fail was not a goto error, it was a block error
       | (named because a cleanup statement `goto fail;` always executed
       | because of a brace-less if). goto can be abused like any other
       | language construct, but it's uniquely useful and makes code more
       | simple and easy to reason about when used correctly. You just
       | shouldn't be using C any more unless under duress.
        
         | preseinger wrote:
         | of course nobody should be writing C any more, for exactly the
         | reason you're demonstrating in this comment
        
           | raldi wrote:
           | What language do you think Linux and Arduino should be
           | programmed in?
        
       | [deleted]
        
       | WalterBright wrote:
       | The C version:                   int* foo(int bar) {
       | int* return_value = NULL;                  if
       | (!do_something(bar))                 goto error_1;             if
       | (!init_stuff(bar))                 goto error_2;             if
       | (!prepare_stuff(bar))                 goto error_3;
       | return_value = do_the_thing(bar);              error_3:
       | cleanup_3();         error_2:       cleanup_2();         error_1:
       | cleanup_1();                  return return_value;         }
       | 
       | The D version:                   int* foo(int bar) {
       | scope(exit) cleanup1();             if (!do_something(bar))
       | return null;                  scope(exit) cleanup2();
       | if (!init_stuff(bar))                 return null;
       | scope(exit) cleanup3();             return do_the_thing(bar);
       | }
       | 
       | https://dlang.org/articles/exception-safe.html
        
         | vore wrote:
         | I find that after writing a lot of Go, manually having to
         | defer/scope(exit) is a lot more error prone than just RAII
         | destructors: it's impossible to forget to defer the destructor.
        
           | WalterBright wrote:
           | The trouble with RAII is when it's necessary to unwind the
           | transactions. See the article I linked to.
        
             | vore wrote:
             | That seems like an inside-out way of doing it to me. I
             | would schedule work onto the transaction struct and make it
             | ultimately responsible for if it should roll back the work
             | or keep it committed.                  let mut tx =
             | Transaction::new();        dofoo(&mut tx)?;
             | dobar(&mut tx)?;        tx.commit();
             | 
             | There is some overhead to boxing the rollback functions for
             | dofoo/dobar into the transaction object, but it's far less
             | error prone (or maybe you can avoid the boxing by encoding
             | all the rollback operations at the type level: less
             | ergonomic but not by much).
        
         | innagadadavida wrote:
         | Plain C version with attribute((__cleanup)):
         | int* foo(int bar) {             int* return_value = NULL;
         | __cleanup__((cleanup_1)) int c1 = 0;             if
         | (!do_something(bar))                 return NULL;
         | __cleanup__((cleanup_2)) int c2 = 0;             if
         | (!init_stuff(bar))                 return NULL;
         | __cleanup__((cleanup_3)) int c3 = 0;             if
         | (!prepare_stuff(bar))                 return NULL;
         | return_value = do_the_thing(bar);                  return
         | return_value;         }
        
           | hgsgm wrote:
           | That's GCC, not standard C, right?
        
             | innagadadavida wrote:
             | Clang works too. Not sure about the rest.
        
         | gregmac wrote:
         | I have a distaste for all of these examples, which comes from
         | the existence of a side-effecting operation: calling
         | do_something() necessitates the need to call a cleanup
         | function, which means there's some state being changed but
         | hidden behind the internals of these methods. It is really easy
         | to call this incorrectly which says to me it's just a badly-
         | designed API.
         | 
         | In C# the idiomatic way would be to have each of these 3 things
         | be defined in a class using IDisposable, which is similar to
         | D's scope() -- the declaring class gets a cleanup method when
         | the variable goes out of scope, no matter how that happens.
         | 
         | I assume there's some interaction between these classes, but
         | IMHO that should be explicitly defined and so the code would
         | look something like:                   public void foo(int bar)
         | {             using var something = new Something(bar)
         | if (something.do()) {                 using var stuff = new
         | Stuff(bar);                 if (stuff.init()) {
         | using var stuff2 = new Stuff2(bar); // two "stuff"s looks dumb
         | but this is example code                     if
         | (stuff2.prepare()) {                         return
         | do_the_thing(something, stuff, stuff2, bar);
         | }                 }             }                  return null;
         | }
         | 
         | There's actually several ways to structure this code which
         | would result in something that looks better than the above, but
         | being example code and not knowing how `something` and `stuff`
         | interact, it's hard to write this nicely. I'd probably aim for
         | something much more concise like:                   public void
         | foo(int bar) {             using var something = new
         | Something(bar);             using var stuff = new
         | Stuff(something);             using var stuff2 = new
         | Stuff2(stuff);                  return stuff2.prepare() ?
         | do_the_thing(stuff2) : null;         }
         | 
         | In the above, I assume stuff2.prepare() calls everything it
         | needs to on the dependent objects, but how I'd structure this
         | for real entirely depends on what they're actually doing.
        
           | marcosdumay wrote:
           | In C# it's customary just to wave away the worst kinds of
           | problem that C and D developers try to handle, and let the
           | runtime kill your program. (This is more an artifact of why
           | people pick their languages than anything inherent on the
           | languages themselves.)
           | 
           | But rest assured, your C# code _is_ full of global state
           | hidden on its runtime and is subject to the same kinds of
           | errors people are discussing here.
        
             | hgsgm wrote:
             | "hidden on its runtime" keeps it out of the rest of thr
             | program.
        
               | marcosdumay wrote:
               | The entire program still has the same failure modes. If
               | you wanted to handle them, you would get the same
               | problems.
        
         | rcme wrote:
         | Do scopes work without linking against the D runtime?
        
           | WalterBright wrote:
           | yes, for `scope(exit)`. Here's the assembler generated:
           | push    RBP                     mov     RBP,RSP
           | sub     RSP,020h                     mov     -020h[RBP],RBX
           | mov     -018h[RBP],R12                     mov
           | -010h[RBP],R13                     mov     -8[RBP],EDI
           | mov     EBX,-8[RBP]                     mov     EDI,EBX
           | call      _D5test312do_somethingFiZi@PC32
           | test    EAX,EAX                     jne     L2F
           | xor     R13D,R13D                     mov     EBX,1
           | jmp short       L79         L2F:        mov     EDI,EBX
           | call      _D5test310init_stuffFiZi@PC32
           | test    EAX,EAX                     jne     L4A
           | xor     R13D,R13D                     mov     R12D,4
           | mov     EBX,4                     jmp short       L68
           | L4A:        mov     -8[RBP],EBX                     mov
           | EDI,-8[RBP]                     call
           | _D5test312do_the_thingFiZPi@PC32                     mov
           | R13,RAX                     mov     R12D,7
           | mov     EBX,7                     call
           | _D5test38cleanup3FZv@PC32         L68:        call
           | _D5test38cleanup2FZv@PC32                     cmp     R12D,4
           | je      L79                     cmp     R12D,7
           | jne     L8B         L79:        call
           | _D5test38cleanup1FZv@PC32                     cmp     EBX,1
           | je      L8B                     cmp     EBX,4
           | je      L8B                     cmp     EBX,7         L8B:
           | mov     RAX,R13                     mov     RBX,-020h[RBP]
           | mov     R12,-018h[RBP]                     mov
           | R13,-010h[RBP]                     mov     RSP,RBP
           | pop     RBP                     ret
        
         | bitwize wrote:
         | It's still wrong by default.
         | 
         | The correct pattern here is RAII, which requires no explicit
         | cleanup code so there's no forgetting to use it.
        
           | aliceryhl wrote:
           | The article specifically talks about C, which does not have
           | RAII.
        
           | WalterBright wrote:
           | D does support RAII. But RAII has a problem: If you want
           | transactions A and B to be both successful, or both are
           | unwound, using RAII is a clumsy technique. It gets much worse
           | if you need A, B and C to either all succeed or all fail.
           | This article goes into detail:
           | 
           | https://dlang.org/articles/exception-safe.html
        
       | Groxx wrote:
       | Alternative 6: keep a list of closures to execute before
       | returning. Then regardless of the length or complexity, it's
       | always just `cleanup(); return`.
       | 
       | For a structured version of ^ that, see Go's defer.
        
       | jitl wrote:
       | There have been a bunch of major security vulnerabilities due to
       | mistakes involving GOTO in C, being used as suggested by the
       | article. Here's a memorable one:
       | https://www.imperialviolet.org/2014/02/22/applebug.html
        
         | bazoom42 wrote:
         | That error is not due to goto, it was just a goto which was
         | errously executed because of badly formatted code. (It looked
         | like the statement was inside an if-block due to the indent.)
         | 
         | Pyhon would have prevented this bug, but so would a formatter.
         | Rust also requires braces for if-blocks to prevent this kind of
         | error.
        
           | bitwize wrote:
           | "The problem isn't C, it's that they weren't using C
           | properly..."
        
             | Karellen wrote:
             | That's not really a fair complaint against C, when many
             | safer languages whose syntax derives from C (e.g.
             | javascript) would have the same problem.
        
               | benj111 wrote:
               | It's a fair complaint against C _and_ JavaScript.
        
             | lolcatuser wrote:
             | But that _is_ a legitimate problem.
             | 
             | Using a language feature that is a known footgun (`if ...`
             | instead of `if {...}`) without being cautious enough to
             | avoid shooting yourself in the foot is not the fault of the
             | footgun, it's the fault of the programmer.
             | 
             | Additionally, in the above linked case, the problem isn't a
             | misused `goto`, it's a misused `if ...`. It would be just
             | as problematic if they typed `cleanup_context();` instead
             | of `goto fail;`, but nobody complains about cleaning up
             | state, do they?
        
               | preseinger wrote:
               | Once a footgun shoots the feet of enough people, the
               | problem is no longer with the people, it is with the
               | thing that enables the footgun.
        
         | olliej wrote:
         | "Goto fail" would have been wrong in a non-goto language that
         | use RAII.
         | 
         | The problem was not goto, it was that the else path was also
         | "give up". In a non goto/RAII/defer language it would have been
         | something like                   If (error)             Return
         | Return
         | 
         | My assumption has been this was some kind of merge error rather
         | being wrong off the bay. Interestingly mandatory indenting or
         | mandatory braces might have stopped this, but then i would have
         | thought -Werror with the dead code warnings would have as well
         | :-/
         | 
         | But again the error was not the goto, and believing it was is
         | the exact problem the article is talking about: people are so
         | opposed to goto they are unable to see real issues (I have seen
         | C code that tries to avoid goto completely and error handling
         | code becomes horrific, far more complex, and far more error
         | prone that just using goto). The problem with goto is that it
         | is very easy to use it unnecessarily, in ways that complicated
         | control flow but don't actually make things better.
        
         | camel-cdr wrote:
         | if considered harmful
        
           | eckza wrote:
           | Expressions that aren't required to return a value for all
           | possible branches of execution that can also mutate state
           | considered harmful.
        
         | ouid wrote:
         | I wondered then if this couldn't have been a merge error.
        
           | olliej wrote:
           | That's been my assumption as well.
        
         | RicardoLuis0 wrote:
         | that one in particular was not really caused by goto, but
         | rather by braceless if statements, it'd be a vulnerability all
         | the same if the line was a "fail" function that was called
         | instead of a goto.
        
         | Swenrekcah wrote:
         | I'd say this was more due to the case not being properly
         | enclosed in brackets.
         | 
         | Many people seem to really dislike using the brackets but this
         | is what that gets you.
        
           | arp242 wrote:
           | Lack of code review and testing always seemed like the most
           | pressing problems with that; both of which should have caught
           | this error. Yes, it probably also should have had braces, but
           | that seems like the lesser issue.
        
           | kevin_thibedeau wrote:
           | This is detected by -Wmisleading-indentation now.
        
             | anthomtb wrote:
             | For the curious this flag has been available since GCC 6.1
             | (2016) and Clang 10 (2020). It is enabled by -Wall for both
             | compilers.
             | 
             | Interestingly (or perhaps coming full circle), the bug
             | reference by the GGP comment is also called out in the GCC
             | 6.1 release notes:
             | 
             | > -Wmisleading-indentation warns about places where the
             | indentation of the code gives a misleading idea of the
             | block structure of the code to a human reader. For example,
             | given CVE-2014-1266                 sslKeyExchange.c: In
             | function 'SSLVerifySignedServerKeyExchange':
             | sslKeyExchange.c:629:3: warning: this 'if' clause does not
             | guard... [-        Wmisleading-indentation]         if
             | ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
             | ^~       sslKeyExchange.c:631:5: note: ...this statement,
             | but the latter is misleadingly indented as if it is guarded
             | by the 'if'             goto fail;             ^~~~
        
         | chclt wrote:
         | This seems to be more an issue with if statements without curly
         | braces.
        
       | spacedcowboy wrote:
       | I'm browsing this, and I'm not seeing the way I do it, which is
       | sort-of-like #5 but not quite... I tend to wrap the code that has
       | multiple exits-to-label in a do...while(0) loop, and use break to
       | get there...
       | 
       | So it might look like:                 do {         if (false ==
       | call_func1()) {           cleanup_any_state();           break;
       | }              if (false == call_func2()) {
       | cleanup_any_state();           break;         }            }
       | while (0);
       | 
       | At any point you can branch to the common exit-statement, and
       | keep on testing for failure as you go through the algorithm
       | without indenting forever.
       | 
       | Generally there's no too much state to clean up in the code I've
       | been using this in, but obviously later 'break' conditions would
       | have to clean up the state for earlier ones too. That's easy to
       | abstract into functions for the cleanup though.
        
         | gleenn wrote:
         | At this point couldn't you just write an actual function and
         | use "return"?
        
           | spacedcowboy wrote:
           | So the sort of stuff I've been using this in is
           | encryption/decryption, where there's a whole boatload of
           | things you have to set up, read/create OIDs, configure
           | identities, fetch certificates, match algorithms, etc. etc.
           | 
           | All of that has to be ok before you finally get to the bit
           | that does the work, and since I'm using ObjC with its ARC
           | feature, I don't need to deallocate anything, they'll be
           | deallocated as they go out of scope. I tend to release
           | critical RAII stuff in my -dealloc method anyway if there's
           | anything there to be done.
           | 
           | So it's really a whole long list of                 id result
           | = nil;       do {         if (setup-X-fails)           break
           | if (setup-Y-fails)           break              ...
           | result = call_method(X,Y,Z,A,...F)       } while (0);
           | 
           | ... which works out pretty well, and is very readable. The
           | setup-xxx stuff can be several pages of code for each method
           | - and useful in their own right, so integrating it into the
           | loop doesn't seem preferable.
        
       | echelon wrote:
       | > Bad code is the product of bad programmers
       | 
       | This person is living in a pretend bubble that isn't grounded in
       | the reality of large projects, multiple team members, deadlines,
       | changing requirements, etc.
       | 
       | No programmer is perfect. And when your tool can cut your arm
       | off, you should be careful or route around the dangerous bits
       | when possible.
        
         | galangalalgol wrote:
         | Yeah, I don't know how people say stuff like this without
         | looking over their shoulder or knocking on wood.
        
         | jeroenhd wrote:
         | I see this "if you're good then you don't need safety"
         | mentality in a lot of conversations with C programmers about
         | programming languages.
         | 
         | Maybe it's some kind of defencive response against newer
         | programing languages slowly eating up spaces C used to be
         | dominant in (like command line tools and system daemons), maybe
         | it's just the programmer saying this thinking they're _that_
         | special. Either way, most people saying this are setting
         | themselves up for failure.
         | 
         | I wouldn't start a new project in C unless I absolutely have to
         | but if you disagree, you can at least admit that there are
         | dangers in C that you need help with if you want to be sure
         | you're doing everything right. With most warnings treated as
         | errors, extended warnings enabled, linting to make things like
         | missing brackets obvious, static analysis of all code to spot
         | difficult bugs, automated dynamic analysis of test cases and a
         | proper testing pipeline I believe you can write C code that's
         | safe enough.
         | 
         | In this case, I'm willing to give the authors the benefit of
         | the doubt because goto hatred is worse than the risk posed by
         | goto in most settings. Gotos used right are fancy if/switch
         | statements and avoiding them in C can lead to a mess that
         | doesn't add much safety. Most examples given are better in my
         | opinion, because using goto can replicate the code flow modern
         | languages provide with things like when/match/defer keywords.
        
         | wruza wrote:
         | This analogy implies that all other tools are 1000% safer, but
         | they are not. In C it's like pointing to a dusty corner behind
         | a table in a room full of dirt.
         | 
         | When was the last time you did "cut your arm" with goto
         | specifically? What's the count and time ratio to other issues?
         | Were these also addressed as taboo or left as "experience
         | earned"? Gotophobia in its largest part is just a stupid meme
         | with no real world data.
        
           | echelon wrote:
           | Where do I imply other tools are 1000% safer?
           | 
           | Even 30% safer is a win.
           | 
           | 30% safer, 30% more readable, and 30% more productive would
           | be even better.
           | 
           | > When was the last time you did "cut your arm" with goto
           | specifically?
           | 
           | It's been a while since I've used C, and even longer since
           | I've personally written goto statements. I do remember
           | frequently getting tripped up on them right after undergrad.
           | It's not friendly, and I don't ever wish to touch them again.
           | 
           | I'm working in a C++ game engine project right now and it's
           | constantly segfaulting. I can't imagine that setting register
           | jumps manually in complex higher level code would improve my
           | situation.
           | 
           | When I get to choose the language, I use Rust. It fits the C
           | use case and fixes many of the warts.
        
             | wruza wrote:
             | _I do remember frequently getting tripped up on them right
             | after undergrad. It 's not friendly, and I don't ever wish
             | to touch them again._
             | 
             | So it's something bad from the undergrad past, no details.
             | Must we take an advice based on that? I'm not sure I will.
             | 
             |  _I 'm working in a C++ game engine project right now and
             | it's constantly segfaulting. I can't imagine that setting
             | register jumps manually in complex higher level code would
             | improve my situation._
             | 
             | Neither would tabooing something based on weak or no
             | evidence. "It doesn't help here" and "we ban it and
             | ostracize its use" are two different claims.
        
           | avgcorrection wrote:
           | > This analogy implies that all other tools are 1000% safer,
           | but they are not.
           | 
           | What an oddly specific presumption.
        
         | cube00 wrote:
         | It's right up there with being told you shouldn't need any
         | access to production if you programmed it correctly the first
         | time.
        
       | wheresmycraisin wrote:
       | Exceptions are GOTO's. Sure, it's more likely to be safe in terms
       | of memory/resource leaks, but in terms of program logic it is no
       | different.
        
         | bazoom42 wrote:
         | Returns are also gotos then.
        
           | wheresmycraisin wrote:
           | If they are anywhere except once at the very end of the
           | function, then yes they are logically like GOTO's.
        
       | nickdrozd wrote:
       | The problem with _goto_ is that it is an unbelievably primitive
       | operator. It can be used to implement any logic at all, and
       | therefore it does not express any logic very clearly. It 's a bad
       | way to express intent in code. Aside from the exceptions
       | discussed in the article, there is always a better, clearer way
       | to express logic than to use _goto_ statements.
       | 
       | The same is true of _while_ loops. Aside from a few cases where
       | they are required, they are always better rewritten with a less
       | primitive operator ( _for_ , etc). The arguments that programmers
       | today make in defense of _while_ are quite similar to the
       | arguments programmers used to make in defense of _goto_.
        
         | kazinator wrote:
         | This is false. Tail recursion is clearer and easier to reason
         | about than loops, and tail recursion can be rewritten into
         | gotos. I mean rewritten in a direct way, where the shape of the
         | logic is the same.
         | 
         | E.g. even an odd problem. Let's use GNU C with local functions:
         | #include <stdbool.h>            bool even(int x)       {
         | auto bool odd(int x);         bool even(int x)         {
         | if (x == 0)             return true;           else
         | return odd(x - 1);         }         bool odd(int x)         {
         | if (x == 0)             return false;           else
         | return even(x - 1);         }              return even(x);
         | }
         | 
         | Using goto: achieved by a mechanical transformation involving
         | just some local edits:                 bool even(int x)       {
         | goto start;              even:         {           if (x == 0)
         | return true;           else           {             x = x - 1;
         | goto odd;           }         }         odd:         {
         | if (x == 0)             return false;           else
         | {             x = x - 1;             goto even;           }
         | }              start: goto even;       }
         | 
         | Every tail-called local function just becomes a block headed by
         | a goto label. The tail call is replaced by assigning a new
         | value to every argument variable and performing a goto. Someone
         | who is briefed on the approach here can easily see the original
         | tail recursion and maintain the code in such a way that the
         | tail recursion could always be recovered from it.
         | 
         | There was a discussion several years ago in comp.lang.c where a
         | problem was proposed: using whatever approach you see fit,
         | write a C program which strips comments from C code, but
         | preserves everything, including preprocesor directives.
         | Something like that. The person who proposed the problem
         | refrained from posting his solution for several days. He used
         | tail recursion for the entire state machine of the thing (even
         | avoiding if statements; all the cases in the tail functions
         | were handled by the ternary ?: operator).
         | 
         | Others used structured programming: nested loops and such. My
         | solution used goto.
         | 
         | I argued that the goto solution had all the good properties of
         | the superior tail calling solution.
         | 
         | I then supported my argument by writing a text filter which
         | converted that person's tail call program into one with a big
         | function containing goto blocks (compiling and producing the
         | same result and all). A reverse filter would be possible also.
         | 
         | I believe that we can take any mess of a goto graph, divide it
         | into the labeled nodes, round up the variable and everything
         | being done to them and express it as tail recursion.
         | Ironically, the one thing that will make it a bit harder is
         | structured control flow constructs like while, for, switch and
         | what not, where we may have to rewrite those to explicit goto
         | first! E.g. if we look at a while loop, it's like a tail call,
         | but one which is invisible. The end of the while loop body
         | invisibly tail calls to the start, which is bad for
         | understanding.
         | 
         | The thing that will detract from the ability to understand the
         | tail call graph is excessive parameters. In the worst case,
         | every tail function will have to take all of the state
         | variablews as parameters, and pass them all to the next tail
         | function (except for altering some of them). There is a pass
         | that can be done over that to reduce some of these. Like if
         | some tail function foo(a, b, c, d, e, f) doesn't do anything
         | wiht c d e f other than pass it to children and none of those
         | children do anything with those variables (transitively), we
         | can cull those parameters from foo and all the children. This
         | is the hard thing to understand in goto graphs: which of the
         | numerous state variables are relevant to where the goto is
         | going?
         | 
         | Some state vars can be replicated and localized. E.g. in our
         | even() example, we can do this:                 bool even(int
         | x)       {          // params of even:         int x0;
         | // params of odd         int x1;              goto start;
         | even:         {           if (x0 == 0)             return true;
         | else           {             x1 = x0 - 1;             goto odd;
         | }         }         odd:         {           if (x1 == 0)
         | return false;           else           {             x0 = x1 -
         | 1;             goto even;           }         }
         | start:         {           x0 = x;           goto even;
         | }       }
         | 
         | Now we no longer have the same variable on both sides of an
         | assignment. Each block works with its private parameter
         | variable. The other block only every assigns to that variable
         | when simulating parameter passing: e.g. the odd block assigns
         | to even's x0 just before goto even.
         | 
         | We can start with a goto graph and make incremental
         | improvements like this and recover a tail call graph. We can
         | then try to understand what the tail functions mean in terms of
         | recursion and document that.
        
           | preseinger wrote:
           | > Tail recursion is clearer and easier to reason about than
           | loops
           | 
           | tail recursion is a complex and subtle expression of control
           | flow that requires substantial background knowledge to be
           | able to even understand, much less reason about based on code
           | on a page
           | 
           | for loops are immediately intuitive to anyone, even without
           | any programming training
           | 
           | no idea how you can come to this conclusion. just ain't so
        
       | pornel wrote:
       | Manual goto cleanup is such a busywork, adding nothing of value,
       | only places for potential leaks and UAFs.
       | 
       | I know for C it's unthinkable to standardize such a luxury like
       | defer or destructors, so we're going to relive arguments from
       | 1968 for as long as C is used.
        
         | hgs3 wrote:
         | > I know for C it's unthinkable to standardize such a luxury
         | like defer or destructors, so we're going to relive arguments
         | from 1968 for as long as C is used.
         | 
         | There was a proposal for defer in C23 but it didn't make the
         | cut [1]. There is also the __cleanup__ attribute if you're
         | using GCC.
         | 
         | [1] https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2895.htm
        
       | kazinator wrote:
       | The TXR project has 402 gotos, not counting ones found in
       | Flex/Bison generated files.                 0:[0226:084752]:sun-
       | go:~/txr$ git grep '\<goto\> ' '*.c' '*.h' '*.l' '*.y' | wc
       | 402    1386   12866
        
       | maldev wrote:
       | This blogpost is horrible! The title is good, you can tell if
       | someone actually uses C at a decent level based off of if they
       | describe SESE(single exit single entry) and how you use goto's to
       | achieve that.
       | 
       | BUT, the fact they have multiple goto locations in one function
       | violates this! Only one goto locations ! That goto is goto
       | cleanup, or goto exit. What you do is then check state of each
       | variable you cleanup. Every function should be some variable of
       | this. If anyone writes C in any other style than SESE, you can
       | consider them a subpar C programmer. There's variations like
       | using BOOL and in and out variables. I like them, but there are
       | different styles. But anyone not using a single AND ONLY A SINGLE
       | goto in every function is 100% a subpar C programmer who you
       | should not trust.                   BOOL foo()         {
       | int *allocation;            char *allocation2;            BOOL
       | bRet = FALSE;            const int BUFFSIZE = 10; //NO MAGIC
       | NUMBERS            allocation = resourceallocation(BUFFSIZE); //
       | Malloc, file.open, network open, etc            if(!allocation)
       | {               DEBUGPRINT("ALLOCAITON FAILED");
       | bRet = FALSE; //Redundent, but protect against intern
       | goto cleanup;            }                 allocation2 =
       | resourceallocation2(BUFFSIZE); // Malloc, file.open, network
       | open, etc            if(!allocation2)            {
       | DEBUGPRINT("ALLOCAITON FAILED");               bRet = FALSE;
       | //Redundent, but protect against intern               goto
       | cleanup;            }                     ...            bRet =
       | TRUE;         cleanup:            //Add error handling if
       | allocation fails            if(allocation)
       | resourcefree(allocation);            if(allocation2)
       | resourcefree(allocation2);                          return bRet;
       | }
        
         | tsegers wrote:
         | Would you say that the Linux kernel is written by mostly "100%
         | subpar C programmers"? Because it's an extremely common pattern
         | to have multiple goto labels at the end of a function.
        
           | maldev wrote:
           | Yes. There's a reason pretty much every secure C coding
           | standard dictates exact what I said, like CERT C etc. There's
           | a reason they have weird bugs. Just because it's an
           | impressive piece of software, doesn't mean it can't have
           | horrible design pattern written by substandard coders. And in
           | an open source project with as many contributors as Linux, I
           | would say it's not hard to fathom that there's a significant
           | number of substandard people writing code on that codebase.
           | Even MISRA quoted in the article I believe intends that you
           | only have one goto location.
           | 
           | For a big example of substandard coding, see this thread for
           | an egregious wireguard module in BSD. Countless other
           | examples. https://news.ycombinator.com/item?id=33381949
        
             | benj111 wrote:
             | or maybe its that things like a kernel reasonably need to
             | use goto?
             | 
             | or at least at the time it was written, there werent
             | alternatives that were performant enough.
        
       | dhosek wrote:
       | It's interesting to think that when I wrote DVIView (TeX DVI
       | previewer running on VM/CMS) in Pascal/Web in 1987, I found goto
       | to be an absolute necessity (although part of that was doubtless
       | because the relevant logic was closely modeled on Knuth's DVItype
       | which also used goto. I would have a hard time coming up with the
       | last time that I needed a goto since then (or, for that matter,
       | labeled break/continue).
        
       | wernsey wrote:
       | The thought occurred to me the other day that assembly and BASIC
       | share a lot of similarities in how you need to think of your
       | program's flow, yet we ended with a world where assembly is
       | considered respectable while BASIC basically (pardon) got burnt
       | at the stake.
       | 
       | I've been reading a lot about retro gaming lately, so I'm just
       | thinking in the context of bedroom coders of the 80's that
       | learned to program in BASIC, then moved on to assembly to get
       | more performance, and then later moved on to C and C++ as
       | projects became more complicated. They all seem to have turned
       | out okay.
       | 
       | I suppose what I'm getting at is you can write bad code in any
       | language.
        
       | ufo wrote:
       | I always encourage people to go read Dijkstra's GOTO paper,
       | instead of just its title. It's a short and easy read, almost
       | like a blog post. If you pay attention, you can see that the he
       | was talking about spaghetti code vs structured code. It's better
       | when the lexical structure of the source code maps to the
       | execution structure. That is, if you know what is the current
       | line being executed, you have a good idea of what the global
       | state is, which lines executed before this one, and which will be
       | executed next.
        
         | arp242 wrote:
         | The thing is that many people today have never encountered the
         | sort of spaghetti code that Dijkstra was talking about in 1968.
         | There's plenty of confusing and messy code around, but true
         | spaghetti code that GOTOs all over the place and is nigh-
         | impossible to follow has been extremely rare for a long time. I
         | can't recall encountering it in the last 30 years.
         | 
         | It easy to misunderstand what he was even talking about because
         | the paper is so short, assumes you know about this context, and
         | has no concrete examples. People quite reasonably assume it's
         | about ugly code they've encountered, but it's actually about
         | ugly code of a completely different kind.
         | 
         | I'm not that old, but I was unlucky enough to have programmed
         | in an unstructured language where GOTO was the _only_ way to
         | use faux-subroutines in my teens. Whatever you think as code
         | that 's difficult to follow: it's nothing compared to this.
        
           | zh3 wrote:
           | I recall having to sort out spaghetti Fortran back in the
           | '80s; numbers as labels (a la basic but with free-form
           | numbering), computed gotos back and forth in the code, stuff
           | like that. Learnt a lot from fixing that mess.
           | 
           | Forty years later, I'll still use a C goto if the situation
           | warrants (e.g. as a getout from deep but simple if). Maybe
           | because having long been an assembler programmer as well,
           | goto's are part of the landscape (if/else is effectively a
           | conditional and unconditional branch/jump).
        
           | ivalm wrote:
           | Do you know of a good example you could link?
        
             | svieira wrote:
             | The first part of Guy Steele's talk on Fortress:
             | https://www.infoq.com/presentations/Thinking-Parallel-
             | Progra...
        
             | klyrs wrote:
             | I dug around a little and found an example [1] on a reddit
             | thread looking for examples of spaghetti code. Most of the
             | examples on the thread were just badly written code.
             | Irreducible spaghetti code tends to be complex state
             | machines that cannot be rendered well in a flat format.
             | People like to flatten those out with a trampoline
             | pattern[2], but that can hinder performance.
             | 
             | Malicious spaghetti involves transformations such as
             | for (x = 0; x < 10; x++) {            for (y = 0; y < 20;
             | y++) {                printf("%d %d\n", x, y);            }
             | }             |        | DRY        v             x = 0;
             | loop_x_head:        condition_val = x;
             | condition_stop = 10;        condition_var = 'x';
             | goto check_condition;        loop_x_body:        y = 0;
             | loop_y_head:        condition_val = y;
             | condition_stop = 20;        condition_var = 'y';
             | goto check_condition;        loop_y_body:        printf("%d
             | %d\n", x, y);        increment_val = y;
             | condition_stop = 20;        increment_var = 'y';
             | goto increment;        loop_y_end:        increment_val =
             | x;        condition_stop = 10;        increment_var = 'x';
             | goto increment;        loop_x_end:        halt;
             | increment:        increment_val++;        if (increment_var
             | == 'x')            x = increment_val;        if
             | (increment_var == 'y')            y = increment_val;
             | condition_val = increment_val;        condition_var =
             | increment_var;        check_condition:        if
             | (condition_val < condition_stop)            goto
             | pass_condition:        if (condition_var == 'x')
             | goto loop_x_end;        if (condition_var == 'y')
             | goto loop_y_end;        pass_condition:        if
             | (condition_var == 'x')            goto loop_x_body;
             | if (condition_var == 'y')            goto loop_y_body;
             | 
             | [1] http://wigfield.org/RND_HAR.BAS
             | 
             | [2] https://en.wikipedia.org/wiki/Trampoline_(computing)
        
             | arp242 wrote:
             | Something like this is the best I could find: https://craft
             | ofcoding.files.wordpress.com/2018/01/fortran_go... - the
             | last page has a full listing with arrows to show the jumps.
             | 
             | I grew up with MSX-BASIC:
             | https://github.com/plattysoft/Modern-MSX-BASIC-Game-
             | Dev/blob... - GOSUB jumps to a specific line number (RETURN
             | returns from where it jumped). Even a fairly simple and
             | clean example like this can be rather difficult to follow.
        
           | [deleted]
        
           | pavlov wrote:
           | This.
           | 
           | To find an example, I did a search for "Commodore PET Basic
           | programs".
           | 
           | Here's a book from 1979 that shows what spaghetti code looks
           | like, in my opinion:
           | 
           | http://www.1000bit.it/support/manuali/commodore/32_BASIC_Pro.
           | ..
           | 
           | The first program listing is on page 24 of the PDF. Try to
           | follow the logic of the program. Why does line 400 go to 280?
           | What paths can lead to line 400? Who knows! And this is high-
           | quality BASIC by 1979 standards -- it's in a printed book
           | after all.
           | 
           | There's an auxiliary listing after the program itself
           | explaining the routines and variables used, but many/most
           | programs in those days wouldn't have this level of rigorous
           | documentation. Deciphering the program would probably have to
           | start by drawing a flowchart of execution paths.
        
             | Someone wrote:
             | > And this is high-quality BASIC by 1979 standards -- it's
             | in a printed book after all.
             | 
             | I don't think that's true, certainly not for books of that
             | time period. Because the whole field was changing rapidly,
             | writers would often work under tight schedules, and
             | customers would buy about anything because they only had
             | magazines and books to learn from and review sites didn't
             | exist.
             | 
             | I also think that's bad Basic for the time. Certainly,
             | comment lines before subroutines would help.
        
               | pavlov wrote:
               | I would argue that the average program that got printed
               | in a book was probably quite bad but still of a higher
               | quality than the programs people wrote on their own,
               | simply because the latter were usually written without
               | any education or useful models of working programs.
               | 
               | It's like an iceberg of bad code: the underwater part
               | nobody saw was astonishingly terrible by modern
               | standards. That code might be running a business, but its
               | author would never get exposed to professional
               | programming. Today Excel often serves a similar purpose.
               | (Excel isn't spaghetti though since the execution model
               | is completely different.)
        
             | Closi wrote:
             | Worth also noting the sort of wild limits with Basic too
             | that are partially responsible for the code being
             | spaghetti, including effectively an inability to add new
             | lines in the code without adding a goto between previous
             | statements.
        
             | anigbrowl wrote:
             | Wow, that takes me back. I learned to program on a PET and
             | would have devoured this book had it been available. As it
             | was one of our math teachers was tasked with teaching the
             | computer classes bu didn't have any programming knowledge
             | beyond input/output, loops, and simple calculations. Books
             | and manuals were hard to come by.
        
               | lamontcg wrote:
               | Also learned to program on a PET in BASIC writing code
               | that was much messier than this because I was probably 10
               | years old.
               | 
               | Might be why I'm good at restructuring horrible to read
               | spaghetti code.
        
           | jjav wrote:
           | > The thing is that many people today have never encountered
           | the sort of spaghetti code that Dijkstra was talking about in
           | 1968.
           | 
           | Can't highlight this enough. The type of spaghetti code "goto
           | considered harmful" was reacting to is basically impossible
           | to create anymore, so anyone who didn't work on that type of
           | code in the 80s or earlier probably hasn't seen it.
           | 
           | And thus, is applying the mantra "goto considered harmful"
           | incorrectly. (Such as trying to avoid it in C for clean error
           | handling, when there's no reason to avoid that.)
           | 
           | To try to replicate that experience, you'd have to write your
           | entire application (including all libraries since libraries
           | were often not a thing) as one single function in C. All of
           | it, no matter how many tens of thousands of lines of code,
           | all in one function. Then label every line. Then picture
           | having GOTOs going every which way to any of the labels. For
           | instance you'd preset the counter variable to some desired
           | value and jump right into the middle of a loop elsewhere.
           | Over time you'd surely accumulate special conditions within
           | that loop to jump out. And so on. It's difficult to even
           | imagine code like this today (or in the past 30 years).
        
             | piva00 wrote:
             | I agree, the closest modern equivalent is code that
             | branches too much from too many places. Modern tools still
             | help a lot to reason about it but it comes to a place where
             | I, personally, have to create call flow diagrams on pen and
             | paper or a whiteboard to actually understand some flows.
             | 
             | Code with extreme branching, while dealing with
             | state/boolean parameters that determine flow branching;
             | error handling that can also create other branches of
             | execution; all of that is really hard to keep in mind when
             | reading such nightmare codebases...
        
             | strangattractor wrote:
             | If you have ever seen someone try an construct a bunch of
             | nested IF statements with complicated conditional clauses
             | you might think GOTO is not so bad. People have simply
             | become better coders. There are also still GOTOs that are
             | used in specific cases such as CONTINUE and BREAK - no
             | labels required.
             | 
             | If I look back it always comes back to naming and managing
             | names of things. GOTO 100 is meaningless and one eventually
             | runs out of meaningful names for GOTO labels. For me OOPs
             | addressed the naming issue relatively effectively by using
             | the data type as a namespace of sorts.
        
               | lolinder wrote:
               | CONTINUE and BREAK are quite different from GOTO in that
               | they operate predictably given the current scope: their
               | limitations make them incapable of creating the
               | unstructured nightmare that Dijkstra was talking about.
               | They're similar to a GOTO only in that they compile to a
               | jump, but so do IF statements and FOR loops.
               | 
               | Structured programming wasn't about eliminating jumps, it
               | was about enforcing discipline in their use. The simplest
               | way to do that is to eliminate raw GOTO from the
               | language, but it's also possible to just be careful and
               | use it wisely.
        
               | JohnBooty wrote:
               | Not too many things make me shake my head harder than
               | folks who consider continue/break to be GOTO equivalents.
               | For the reasons you eloquently said.
               | 
               | Additionally, far more often than not, continue/break
               | allow you to avoid _another_ form of complexity, bugs,
               | and low comprehensibility: deeply nested conditionals.
        
               | bazoom42 wrote:
               | The argument against gotos in Dijkstras article would
               | apply equally to breaks and continue and even to early
               | returns.
               | 
               | I dont fully agree with Dijkstras argument. For example I
               | think early returns can often improve the readability of
               | the code. But worth noting Dijkstra is not primarily
               | concerned about readability but rather about how to
               | analyze the execution of a program.
        
             | snovv_crash wrote:
             | Sure it's possible to have horrible spaghetti today. Just
             | look at any pubsub architecture based system and tell me
             | what piece of code executes after another. It's super
             | popular, and it's GOTOs all over again, just with data
             | instead.
        
           | spacechild1 wrote:
           | > but true spaghetti code that GOTOs all over the place and
           | is nigh-impossible to follow has been extremely rare for a
           | long time.
           | 
           | Exactly! Recently I had the "pleasure" to work with some
           | FORTRAN IV code from the early 60s, so I know what you mean.
           | No functions/subroutines, only GOTOs. Even loops were done
           | with labels. There is also a weird feature called "arithmetic
           | IF statements" (https://en.wikipedia.org/wiki/Arithmetic_IF).
           | Luckily the code was pretty short (about 500 lines including
           | comments).
        
             | bee_rider wrote:
             | Hmm, it is time to live Cunningham's Law I think:
             | 
             | There's no good way to do a do...while loop in Fortran,
             | other than a goto.
        
           | inlined wrote:
           | I used to work at Microsoft on the windows team. There it was
           | very common to have a "goto cleanup" for all early exits. It
           | was clean and readable. Otoh, I once was assigned to
           | investigate an assertion error in the bowels of IE layout
           | code. It was hundreds of stack frames deep in a recursive
           | function that was over a thousand lines long and had multiple
           | gotos that went forwards or backwards. That was an absolute
           | mess and would have been impossible to debug without an
           | recording ("time travel") debugger.
        
         | [deleted]
        
       | ElfinTrousers wrote:
       | "Well obviously GOTO is dangerous, but not in the hands of a Real
       | Programmer like me!"
        
       | hilbert42 wrote:
       | I was brought up on the GOTO statement in Fortran before the GOTO
       | police outlawed it, so I'm quite familiar with its operation.
       | 
       | Nowadays there's more consideration given to structured
       | programming and that's a good thing but that doesn't necessarily
       | mean that GOTO should never be used--and if it is then it doesn't
       | mean the whole structure of one's program ought to be called into
       | question.
       | 
       | No doubt GOTO can be dangerous and can lead one into bad habits
       | but in certain instances it can simplify code and make it less
       | prone to introduced bugs. Modern coding practice teaches us to
       | recognize and avoid spaghetti code so with those constraints in a
       | programmer's mind he/she should be able to use GOTO effectively
       | and with safety.
       | 
       | The key issue is to know when it's appropriate to use it and when
       | not to.
        
         | atmavatar wrote:
         | GOTO is like profanity: it can be useful when done sparingly,
         | but your peers will start judging you if you rely on it too
         | often.
        
       | williamcotton wrote:
       | Take a look at the error handling in nginx for a good example of
       | how to use goto in C!
        
       | greesil wrote:
       | goto can be replaced by a function call for cleanup.
        
         | olliej wrote:
         | I'm curious what you are calling that can cleanup local
         | variables and temporaries?
        
       | somewhereoutth wrote:
       | Could be worse, could be COMEFROM
       | https://en.wikipedia.org/wiki/COMEFROM
       | 
       | Multiple COMEFROMs referencing the same label are an interesting
       | way to effect multi-threading!
        
       | WalterBright wrote:
       | This is fun! The C version;                   int foo(int v) {
       | // ...             int something = 0;             switch (v) {
       | case FIRST_CASE:  something = 2; goto common1;
       | case SECOND_CASE: something = 7; goto common1;
       | case THIRD_CASE:  something = 9; goto common1;
       | common1:                     /* code common to FIRST, SECOND and
       | THIRD cases */                     break;
       | case FOURTH_CASE: something = 10; goto common2;
       | case FIFTH_CASE:  something = 42; goto common2;
       | common2:                     /* code common to FOURTH and FIFTH
       | cases */                     break;             }         }
       | 
       | The D version:                   int foo(int v) {             //
       | ...             int something = 0;                  void
       | common1() {                     /* code common to FIRST, SECOND
       | and THIRD cases */             }                  void common2()
       | {                     /* code common to FOURTH and FIFTH cases */
       | }                  switch (v) {                 case FIRST_CASE:
       | something = 2;  common1(); break;                 case
       | SECOND_CASE: something = 7;  common1(); break;
       | case THIRD_CASE:  something = 9;  common1(); break;
       | case FOURTH_CASE: something = 10; common2(); break;
       | case FIFTH_CASE:  something = 42; common2(); break;
       | default: break;             }         }
       | 
       | Note the use of nested functions to factor out common code. The
       | nested functions usually get inlined by the compiler, so there is
       | no cost to them. Nested functions are a great way to eliminate
       | gotos without penalty.
        
         | nabla9 wrote:
         | Only some goto use cases.
        
         | Someone wrote:
         | > The nested functions usually get inlined by the compiler, so
         | there is no cost to them.
         | 
         | This kind of code typically gets written when 'usually' isn't
         | good enough (of course, once you use a compiler, in theory,
         | there are no guarantees; the compiler could compile the
         | inlined-function one with a goto or vice versa, but programmers
         | typically are more concerned about what happens in practice)
         | 
         | The inlined functions also may increase code size and
         | instruction cache pressure.
         | 
         | On the other hand, having a branch less may be beneficial.
        
           | Groxx wrote:
           | I agree that programmers care more about what actually
           | happens (and they should, when performance matters!), but
           | this kind of analysis also involves future changes to the
           | compiler unless it's a one-time job. Which sometimes exists,
           | so check the assembly there and do whatever you need.
           | 
           | Straightforward and limited-scope code like nested functions
           | tends to _improve_ in performance over time, because it
           | restricts possibilities better than goto. And it 's more
           | error-resistant to future changes for similar reasons. If
           | your code has to last a while, you're probably better off
           | having the safer one. Or maintain both, and use the safer one
           | to validate the unsafe one, and choose based on benchmarks of
           | the week - what was true when it was written could change
           | with any version.
        
           | WalterBright wrote:
           | When it isn't good enough, inserting `pragma(inline, true)`
           | will force it.
           | 
           | > The inlined functions also may increase code size and
           | instruction cache pressure.
           | 
           | tail merging optimization takes care of that
        
       | BiteCode_dev wrote:
       | I actually prefer "goto-less alternative 2". It's more verbose,
       | but more explicit. No magic, I know exactly what happen. If you
       | suddenly have more functions, you should have an array of
       | functions with a clean_up_level variable.
       | 
       | Of course, you probably should not have a global state that some
       | clean up function with a side effect deals with in the first
       | place, but it's the c linux kernel so I assume there is something
       | I don't know.
        
       ___________________________________________________________________
       (page generated 2023-02-26 23:00 UTC)