21 ms·
GOTOphobia considered harmful in C
- Waterluvian 4y agoI wonder if the GOTO fear is, in part, because in older languages, GOTO used to point to a line number and not a labelled block. The former seems horrifyingly brittle while the latter seems like it could possibly be useful.
- hilbert42 4y agoI 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 4y agoGOTO is like profanity: it can be useful when done sparingly, but your peers will start judging you if you rely on it too often.
- ElfinTrousers 4y ago"Well obviously GOTO is dangerous, but not in the hands of a Real Programmer like me!"
- nickdrozd 4y agoThe 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 4y agoThis 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.
- wernsey 4y agoThe 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.
- kazinator 4y agoThe 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
- deleted 4y ago[deleted]
- jitl 4y agoThere 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 https://www.imperialviolet.org/2014/02/22/applebug.html
- ouid 4y agoI wondered then if this couldn't have been a merge error.
- olliej 4y agoThat’s been my assumption as well.
- RicardoLuis0 4y agothat 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 4y agoI’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.
- kevin_thibedeau 4y agoThis is detected by -Wmisleading-indentation now.
- anthomtb 4y agoFor 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; ^~~~
- echelon 4y ago> 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.
- cube00 4y agoIt's right up there with being told you shouldn't need any access to production if you programmed it correctly the first time.
- galangalalgol 4y agoYeah, I don't know how people say stuff like this without looking over their shoulder or knocking on wood.
- wruza 4y agoThis 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 4y agoWhere 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.
- pornel 4y agoManual 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 4y ago> 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 https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2895.htm
- 0xfedbee 4y agoSorry I’m still not going to use Rust
- williamcotton 4y agoTake a look at the error handling in nginx for a good example of how to use goto in C!
- kmeisthax 4y agoIn 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.
- jolux 4y agoGo doesn't have RAII or destructors, it has defer.
- tedunangst 4y agoWhich definitely does weird things in a loop.
- Groxx 4y agoIIFEs 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 4y agodefer proposal for C made in 2021 https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2895.htm https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2895.htm ... why isn't this in C already?
- dcow 4y agoIf 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 4y agoof course nobody should be writing C any more, for exactly the reason you're demonstrating in this comment
- raldi 4y agoWhat language do you think Linux and Arduino should be programmed in?
- pjmlp 4y agoArduino uses C++, thankfully.
- raldi 4y agoEverything in the top-level comment applies to C++, too.
- pjmlp 4y agoMy "thankfully" remark shows my point of view. The only sin of C++ is the same that plagues evolutionary languages like Typescript, Kotlin and so forth. No matter how many tools they provide to write better code than the language they have grown from, their compatibility with them is like hearding cats while trying to have everyone adopt best practices. Otherwise in regards to Arduino, I would suggest a couple of nice BASIC and Pascal compilers like those sold by Mikroe.
- greesil 4y agogoto can be replaced by a function call for cleanup.
- owisd 4y ago> 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 4y agoThis 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 4y agoC does not guarantee tail call elimination. But you can do the same with an event loop that cleanly avoids goto.
- IncRnd 4y agoNowadays 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#musttail https://clang.llvm.org/docs/AttributeReference.html#musttail [2] https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
- DonHopkins 4y agoGotos are great approach for writing a Flying Spaghetti Monster!
- WalterBright 4y agoThe 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 https://dlang.org/articles/exception-safe.html
- vore 4y agoI 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 4y agoThe trouble with RAII is when it's necessary to unwind the transactions. See the article I linked to.
- vore 4y agoThat 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).
- rcme 4y agoDo scopes work without linking against the D runtime?
- dhosek 4y agoIt’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).
- somewhereoutth 4y agoCould be worse, could be COMEFROM https://en.wikipedia.org/wiki/COMEFROM https://en.wikipedia.org/wiki/COMEFROM Multiple COMEFROMs referencing the same label are an interesting way to effect multi-threading!
- WalterBright 4y agoThis 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 4y agoOnly some goto use cases.
- WalterBright 4y agoDid I say otherwise? D still has a goto, though it is rarely used anymore.
- Someone 4y ago> 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.
- jejones3141 4y agoI 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 4y agoOr even oksofar = oksofar && do_something() to do away with the cumbersome ifs.
- ufo 4y agoI 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 4y agoThe 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.
- pjmlp 4y agoI kind of have, instead of GOTOs, we have RPC calls on the flavour of the day, that just GOTOs only make sense after doing a full diagram of call sequences. Just like 8 bit BASIC spaghetti code, only refined.
- ivalm 4y agoDo you know of a good example you could link?
- svieira 4y agoThe first part of Guy Steele's talk on Fortress: https://www.infoq.com/presentations/Thinking-Parallel-Programming/ https://www.infoq.com/presentations/Thinking-Parallel-Progra...
- BiteCode_dev 4y agoI 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.
- deleted 4y ago[deleted]
- ipaddr 4y agoDrawbacks: additional variables Are we still chasing down the least variables possible in 2023?
- userbinator 4y agoYes, because they multiply state.
- EGreg 4y agoConsidering harmful is starting to be considered harmful, that’s what I am coming around to.
- Groxx 4y agoAlternative 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.
- spacedcowboy 4y agoI'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 4y agoAt this point couldn't you just write an actual function and use "return"?
- spacedcowboy 4y agoSo 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.
- billforsternz 4y agoI don't like the code pattern if ( false == func() ). Function func() already returns a boolean, no need to compare that to a second boolean (false or true) to generate a third boolean. Sorry, a little off topic I know.
- maldev 4y agoThis 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 4y agoWould 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 4y agoYes. 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 https://news.ycombinator.com/item?id=33381949
- zxcvbn4038 4y agoI 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. =)
- MrYellowP 4y agoYou monster. I like you!
- userbinator 4y agoThe 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 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/main/general/toaster/toastDrv/kmdf/bus/static/buspdo.c 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.
- raldi 4y agoThe 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.
- wheresmycraisin 4y agoExceptions 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 4y agoReturns are also gotos then.
- wheresmycraisin 4y agoIf they are anywhere except once at the very end of the function, then yes they are logically like GOTO's.
- randomdata 4y agoNot in the Dijkstra "Go to statement considered harmful" sense. Nor are the goto keywords found in most modern languages. These retain structure and thus are not considered harmful. But there is a good case to be made that exception handlers are gotos in the Dijkstra sense, at least when used for anything other than exceptions, like passing errors around.
- bazoom42 4y agoIf you read Dijkstras letter it is clear that early returns (ie any return which is not the last statement in a function) is subject to the same criticism as goto. > These retain structure and this are not considered harmful This might be your opinion, and it is a very reasonable opionin. But it is just not what Dijkstra is arguing. He is very clearly arguing for “single entry single exit”.
- randomdata 4y ago> He is very clearly arguing for “single entry single exit”. Agreed. Return forces you into a single exit. Upon hitting return, the code can only return back to where the function was originally called. It cannot 'arbitrarily' jump to some other place in code as you could do in an unstructured programming language like, say, BASIC. Which is what Dijkstra was pushing for, being a strong proponent of structured programming. I don't know of any modern programming language that does allow anything outside of a single exit, exception handlers and setjmp/longjmp excepted. There is a good case to be made that the latter two reintroduce the very problem Dijkstra warned of and are generally considered harmful for the same reason.
- User23 4y agoGive me a formal semantics for GOTO. I’ll wait.
- photochemsyn 4y agoI'd guess you could use function pointers to implement the example without goto? https://www.opensourceforu.com/2012/02/function-pointers-and-callbacks-in-c-an-odyssey/ https://www.opensourceforu.com/2012/02/function-pointers-and... Then I looked up 'callbacks considered harmful'. Hmmm.... It's probably easier for an IDE to track down a function if you want to see how the code works, however, and the function might have some useful explanatory comments. Suggested article: "People who claim code can explain itself considered harmful"
- kristopolous 4y agoAnybody that dogmatically avoids something like a cult probably doesn't practice the holistic thinking necessarily to design large systems. People mocking the use of goto is a bozo bit switch for me. It can switch back, but offering pithy out of context absolutisms and trying to pass it off as wisdom is a hard point to recover from. It's almost always in one's interest to play dumb and not be sure of anything. That's what I see the smart people do.
- Timothycquinn 4y agoI had a debate at work about whether guards are considered spaghetti code. The same person also thought that you should never use GOTO's. He learned these rules in his comp-sci classes and never questioned them; even after years in the real world. Personally, I use guards heavily to reduce nesting and if the language supports goto's, I'll use them if it make sense to improve code flow. However, its been a very long time since I've needed to use goto :)
- myth2018 4y agoI would include C# too. Most developers I meet don't even know C# supports GOTO.
- benaadams 4y agoC# (and Go) have adjusted goto to ensure consistent scoping, avoiding undefined variables, and keeping control flow reducible. So its a much safer and better form of goto than exposed in C/C++, where you can still do weird things without being warned by the language
- deleted 4y ago[deleted]
- 752963e64 4y ago[dead]
- ernst_klim 4y agoRecently I've had an argument with somebody bragging about they managed to bypass goto with writing following mess: do { r = allocate_resource(); if (!r) break; } while (false); When I've said that this is just a messy unreadable version of goto, I was told that goto is harmful and this is a structured programming, which is superior. GOTOphobia is real.
- MrYellowP 4y agoIt's not GOTOphobia. It's people blindly doing what others are telling them to do (or not to do) without the ability to critically think about what they're being told. Lots of people just follow whatever anyone, who they perceive as authority, says. Critical thinking isn't a common trait.
- bosch_mind 4y agoMost books like k&r, programming with posix threads, Beej confirm this with C.
- hasmanean 4y agoAs a kid I once saw a quicksort implemented in BASIC and it looked like magic to me. I thought how could anyone come up with this algorithm…. Then I saw it implemented recursively in lisp…it was the simplest most obvious choice to make, and it was hard to imagine anything else. Now I think that the BASIC implementation must have been a translation from the lisp or something equivalent and it would have been very unlikely that any native BASIC programmer would have come up with it on their own. Of course it used GOTOs. With the proper abstraction level any complex algorithm becomes trivial.
- ChrisRR 4y agoI find it similar to the MISRA rules that says there should only be a single return in a function. We make an exception for this at work for checks at the start of a function (eg. NULL, ranges, etc.) and it tends to save an indentation or two. Then generally still follow the single return rule otherwise.