[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)