32 ms·
How to Get Fired Using Switch Statements and Statement Expressions (2016)
- saagarjha 6y agoThe best part is the syntax highlighter struggling to make sense of the code :P
- georgeecollins 6y agoThis is a great example of the dangerous syntax of Switch statements in C. However, I do think programmers should not reflexively avoid Switch/Case statements in their code. Polymorphism is often a way of writing switch statements that looks very clean, but hides some of the same spooky branching behavior. The nice thing about a switch/branch is you can see where the code might go from the text in front of you.
- marcosdumay 6y agoIn C? No, C developers should reflexively avoid switch statements, and only fight this reflex after a serious risk evaluation (and very likely, after a lot of tests are written). Like macros, C switch is too powerful and dangerous to use on a whim.
- simias 6y agoI sorta see where you're coming from, but I can't really see myself avoiding switch entirely in C. I do agree that it's very poorly designed since it makes it very easy to shoot yourself in the foot. It does make it very nice to write some things, in particular state machines. It can even be less error prone in these situations since compilers will (sometimes) warn if you don't match all possible values of an enum, which is nice if you add a state to your machine and forget to update it everywhere. Fortunately these days there are compiler warnings to alleviate some of the risks. GCC with -Wextra will warn if you have an unmarked fallthrough in a switch for instance (but not with -Wall). But beyond that I do agree that if I could go back in time and tell Dennis Ritchie to change some things about the language "make switch break by default and add a fallthrough keyword when you actually want the behaviour" would be very high on the list. I get why he made it that way in the first place (if you implement switch/case in assembly with a jump table you get basically the same behaviour by default and you need additional code to break) but it makes for poor ergonomics in C.
- AnimalMuppet 6y agoBaloney. There is absolutely no need to reflexively avoid switch statements. Just don't do this kind of garbage with them.
- marcosdumay 6y agoAnd never forget a break. And triple check the type of those case labels. And keep them short, so all of the above is verifiable. Or, alternatively, you can default to some syntax where the compiler will help you, and leave switches to use with care only where they bring a huge gain.
- StillBored 6y agoLack of 'break' is a very useful bit of syntax I use somewhat frequently for categorization. AKA you have a handful of case values that fall into a smaller subset of catagories. Rather than an unreadable set of if/else's that all have multiple if "a||b||c||d.." conditions, vs a big state machine, or match structure. Then if one enforces a clearer formatting style than is common on most open source projects the lack of "break" statements stick out visually and it becomes much harder to miss a break statement when the intentionally missing ones have lines like "// Intentional fall-through" Or you use a "nobreak" macro and enforce the use of break/nobreak in the automated style/linter.
- touisteur 6y agoBut if you had a syntax that allowed inner/embedded functions, exhaustive matching, and the ability to combine a|b..e, you might never ever EVER need fallthrough. The case statement in Ada is the best control flow structure (I'm not very familiar with advanced FP-style pattern matching, but I can read and follow Ada's with almost closed eyes, and they're exhaustive by default you won't compile until you've handled all the cases), and Ada2012 added case- and if-expressions, and now it seems there's some ongoing work on making it a bit more powerful... This kind of construction makes the power of enumerated types, and restricted range types so much more evident.
- brundolf 6y agoC is one of those languages (like JavaScript, arguably) that's stuffed full of so many foot-guns you can't possibly hope to make them all statically impossible. So strong conventions need to exist no matter what, and "don't put case statements below the first block" seems like an easy enough one to visually enforce.
- lmilcin 6y agoI also think that every tool has its purpose. Just because it can be misused isn't reason to banish it altogether. In the end, readability and maintainability of the code is primary concern. I don't really care for those fads that say "you should absolutely ban switch statements". I look at these as a collection of tools that help me make the code more readable. Usually radical statements like this come from people that will take the simple naive code and make it completely incomprehensible for exactly opposite effect that the "best practice" was supposed to achieve. Heck, there are even good uses for goto that make the code more readable as long as you follow accepted convention and don't try to be fancy with it. When I spent couple of years working on embedded ANSI C applications I often used it to make complicated inner loops (like reading and parsing input from device) more readable.
- dylan604 6y ago"In this article, we will discuss how you can leverage switch statements and statement expressions to produce C code that is so difficult to understand, you'll need to look at the assembly to figure out what it does. " Go gawd man, how bad is your code that it's easier to read the assembly? I'm not familiar with reading assembly code directly, but in my mind that just reiterates the point the author was making. That's definitely in a special category of bad code.
- czbond 6y agoMakes me think someone wanted to use their "CompSci Assembly" class for the first time ever and show they're an 'alpha engineer'.
- ddingus 6y agoInterestingly, that means just using: GOTO as in jmp #address Would be cleaner.
- beagle3 6y agoI have inherited C++ "class spaghetti" code, which was - in fact -- easier to read the disassembled compiled code than the source code. Because the compiler was often able to prove and inline what actually gets called -- making the code logical (and relatively easy to follow) whereas the source code was abstract nonsense. "spaghetti hierarchy", common in C++ and even more so in Java, is in my opinion and experience, much worse than "spaghetti code" of the old Basic/Fortran and very-early-C days -- the old "goto" spaghetti was hard to follow, but at least every goto named a concrete target. In a spaghetti hierarchy, execution jumps every 2 lines (with actual actions sparsely sprinkled among those lines), but to determine where it goes - you have to keep track of which class/subclass every object was actually instantiated in, and what methods that class/subclass overrides.
- deleted 6y ago[deleted]
- im3w1l 6y agoI mean the fact that it sometimes triggers compiler crashes is kind of a hint that the compiler has no idea what to do and just makes it up as it goes along.
- brianberns 6y ago> Statement expressions ... allow you to embed a compound statement within an expression. The value returned by the last expression is the value returned by the entire statement expression. > You might ask "Why would you ever want to do such a thing?" I just want to mention that expressions are crucial in functional programming, where even an if-statement returns a value. Statements that don't return a value don't make any sense in a pure functional world, because they aren't functions. So hopefully that answers the "why?" question. (Of course, abusing expressions in switch statements, as in this article, isn't something you can do in FP.)
- saagarjha 6y agoWell, in general you use this to write macros that don't evaluate twice.
- pdonis 6y ago> expressions are crucial in functional programming, where even an if-statement returns a value Yes, but in functional programming, expressions can't have side effects, so the problem that the article is discussing in that part doesn't even exist.
- Jtsummers 6y agoYes they can have side effects. It's Haskell and other "pure functional" languages that reduce or eliminate side effects.
- pdonis 6y ago> Yes they can have side effects. I'm not saying expressions can't have side effects in particular languages. I'm saying that, by definition, "functional programming" means that expressions can't have side effects; which means that if you are doing "functional programming", then even if the language you are programming in allows expressions to have side effects, you are not making use of that feature, but are writing your expressions to make sure they don't have side effects, since that is what functional programming requires.
- pfarnsworth 6y agoOne of the first nasty bugs I had to work on when I first came to Silicon Valley was stack corruption from a fall through of a switch statement. After something like that, you learn pretty quick to always put break lines at the end of the switch before anything else.
- cjfd 6y agoAnd then one puts in one too many in a case where fall through actually was the idea....
- panda88888 6y agoI try to be explicit and place a comment saying fall through if it is the intended behavior. This helps to inform the next person reading the code.
- acheron 6y agoSo there was a classic bug in the game NetHack where there was indeed a comment indicating there should be a fall through. Then somebody added a new case, and the fall through now went to the wrong place. But since the comment said it was supposed to fall through, nobody reported the bug. https://nethackwiki.com/wiki/Yeenoghu#.22A_ludicrous_bug.22 https://nethackwiki.com/wiki/Yeenoghu#.22A_ludicrous_bug.22
- climb_stealth 6y agoThere are linters that raise an error when there is a fallthrough without an explicit comment [0]. I feel like that should be a must for working with C code. [0] I don't remember which one it actually was. Possibly something commercial for MISRA compliance.
- cjfd 6y agoActually in C++ there is a fallthrough attribute in the language nowadays. https://en.cppreference.com/w/cpp/language/attributes/fallthrough https://en.cppreference.com/w/cpp/language/attributes/fallth...
- TYMorningCoffee 6y agoHow do switches simulate coroutines? I'm reading thru the article they linked https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html But don't follow how they achieve independent stacks so a caller can continue where it left off in the callee.
- xaedes 6y agoIt is using static variables for coroutine state.
- masklinn 6y ago> But don't follow how they achieve independent stacks so a caller can continue where it left off in the callee. They're "stackless" coroutine, you can only "yield" at the toplevel (where the switch can resume), and each coroutine function can only animate one coroutine because the state is global (it's a static).
- simias 6y agoThe mednafen PSX emulator uses this trick to implement some of the modules (the MDEC and SPU use it IIRC). I always found it hard to follow and personally prefer simpler, more verbose code with explicit state management.
- swiley 6y agoThere's no way to actually create something like call/cc in c so the coroutines can't yield while in a subroutine. It's more like syntactic sugar for for(;;)switch(task){...}
- deleted 6y ago[deleted]
- hinkley 6y agofor(i = 0; i < 10; i++){ case 1:{ I just threw up a little in my mouth. All these years I didn't know such a monstrous thing was legal in C code. Is it possible to make amendments to the Geneva Convention, and if so, who should I call?
- souprock 6y agoSo... I did this: switch(override){ default: if(foo==42){ case THING1: code_here(); }else if(bar&0x42){ case THING2: other_code(); }else{ case THING3: more_code(); } } I thought it was more readable than the alternatives.
- jolmg 6y agoIf override can only be those 3 and they're all non-zero, and default is when override is 0, then the following seems clearer in my opinion. if (override ? override == THING1 : foo == 42) { code_here(); } else if (override ? override == THING2 : bar & 0x42) { other_code(); } else if (override ? override == THING3 : true) { more_code(); }
- deleted 6y ago[deleted]
- deleted 6y ago[deleted]
- predakanga 6y agoThere's a particularly famous instance of this, called Duff's Device[0], with a great quote attached (regarding fall-through in case blocks): > "This code forms some sort of argument in that debate, but I'm not sure whether it's for or against." [0]: https://en.wikipedia.org/wiki/Duff%27s_device https://en.wikipedia.org/wiki/Duff%27s_device
- wahern 6y agoIt's also used to portably implement generators and coroutines. See https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html More generally, the semantics are useful for machine code generation and translation. Computed goto's are even more useful for the above, but they're an extension. I'd love to see computed goto's added to the C standard, but it's far too late to change the semantics of switch. Rather, just accept that their code flow semantics make them slightly more type safe syntactic sugar for goto--not just in how they're implemented, but in how they can be used.
- qw3rty01 6y agoIs the uninitialized read example a compiler bug? `i` in the code is definitely initialized, but the compiler creates a temporary variable, where its initialization is bypassed by the switch jump. Isn't that a code generation issue (specifically where it places the jump label)?
- LorenPechtel 6y agoThis just reinforces my opinion: Programs, like ships, sink in the C.
- Kuraj 6y agoAt risk of being downvoted for not bringing much to the discussion, but holy shit.
- busfahrer 6y agoThis article reminded me of 10+ years ago when I was browsing some MediaWiki code and came across "do { ... } while (false)", which had me flummoxed for a bit until I remembered that PHP has no goto statement. fake edit: I just looked it up and it seems they added it to the language in 2009.
- ChrisMarshallNY 6y agoI remember writing code like that. Basically, I used a switch statement as a goto. I must deeply and profoundly apologize. It will never happen (by me) again.
- jlebar 6y agoMy favorite (not actually horrible) switch statement trick: Instead of string x; switch (y) { case 0: x = "foo"; break; case 1: x = "bar"; break; } try an IIFE! string x = [&] { case(y) { case 0: return "foo"; case 1: return "bar"; } }(); Once you get used to reading it, there are a bunch of advantages. Among them, you can't forget a "break", and you can't forget to assign into `x`.
- mehrdadn 6y agoI think you meant switch(y) instead of case(y)?
- lmilcin 6y agoAlways prefer simple over clever. Both statements do same thing but only one of them is simple to understand and hard to break for a novice developer (or roughly 3/4th of your team).
- mpfundstein 6y agoif your novice dev cant understand the second example, he should be fired on the spot
- Jtsummers 6y agoThey shouldn't be fired, they should be taught. And it depends on what languages they already know and which version of C/C++ they were taught. C++'s lambdas looked very strange to me even though I'd worked with C++ code for 15 years, but nothing newer than C++03 (both what I learned in school and due to code I worked on professionally just being that old). It took me a while and finally sitting down with a couple books on "Modern C++" to grok what was going on with that syntax.
- lmilcin 6y agoOh, wow, what an attitude. Did you consider that it is possible that everybody can read the second example but may need to spend more time reading it to comprehend if they haven't seen it before and may make mistake interpreting the code? Did you consider not every company is Intel or Google and there is a lot of companies that can't get "top 1% talent"? Readability is about making it easy to understand the code, without putting effort into reading. Readability is important because code is written once and red many times by people who may need to read a lot of code and don't want to spend much time trying to understand every line of it.
- quantumet 6y agoMy favorite "extremely compact C" style: if (*len * ("11124811248484"[*type < 14 ? *type:0]-'0') > 4) { ... } because naming lookup tables is clearly too verbose. Among other interesting decisions.
- userbinator 6y agoI pretty much understood that line right away... it's not that different from the code I'm used to reading and writing (embedded, low-level stuff), which just shows that even within C alone there can be a huge range of style from APL-terse to mind-numbingly-Enterprise-Java verbose.
- luord 6y agoAs if I needed more reason to believe that C should absolutely not be the first language taught in CS programs (sadly my alma mater still disagrees). I think I'm gonna have nightmares. Of course, C isn't taught like this over there, but the point is that C gives you way too much rope to hang yourself with, and that's if you're an experienced developer, let alone a college freshman. Sure, the argument can be made that screwing up as a freshman helps you learn in a way that doesn't compromise your career, but I believe that quite a few of my classmates wouldn't have given up on programming altogether had they not been thrown at C right away. I myself didn't learn to love programming until I tried less spartan languages.
- anonymousiam 6y agoStep 1: Slip the code fragment that puts the compiler into an infinite loop into a Git commit for a large collaborative project. Step 2: Watch the fun. Step 3: Profit?
- TheDong 6y agoA git bisect script + 'timeout' would let you find the specific commit in a few minutes. Not to mention CI should prevent that from merging in the first place.
- aarchi 6y agoThis same blog has a post on the idiosyncrasies of the C preprocessor. Reminds me of several convoluted macros I assembled to stress test a C/C++ static analyzer at my last job. My favorite that I wrote was the following, combining line continuations, trigraphs, digraphs, universal character names, and block comments. Particularly insidious is the line continuation between / and * in the block comment. It is equivalent to: #define 🇩🇪() "de" ??/ %: \ /??/ */*\ **/\ def\ ine\ \U\ 0??/ 001\ F??/ 1E9\ ??/\ U00\ 01F\ 1EA\ (??/ ) "\ de"\ This example also exposes undefined behavior from the C++ standard: "if a splice results in a character sequence that matches the syntax of a universal-character-name, the behavior is undefined" [0]. [0]: https://eel.is/c++draft/lex.phases#1.2 https://eel.is/c++draft/lex.phases#1.2
- glitchc 6y agoOh I loved this! Thank you for sharing it.
- spaetzleesser 6y agoUsually when I am planning on getting fired I get drunk every day and make inappropriate comments to my colleagues :) But yes, C allows you to do crazy stuff. Not that you should.
- aasasd 6y agoI've read the first example and the following explanation, and now I'm conflicted between idle desire to know more about programming-related curiosities, and the foreboding of having this atrocity in my head afterwards. It's like seeing gore on Reddit.
- klyrs 6y agoThere are two kinds of C programmers. - folks who haven't seen Duff's device - folks who understand Duff's device Seriously, I was waiting for this article to do something evil but... maybe I'm a horrible person but none of the examples were too bad.
- a1369209993 6y agoWell, the ones involving gotoing into halfway through the evaluation of a expression were bad in that they don't work, or appear to work due to coincidence but will break if slightly perturbed, but they're not hard to understand.
- klyrs 6y agoTo be fair, that's gcc, not C.
- a1369209993 6y agoNo, that's clang/llvm; gcc handles this correctly (at least by the extremely low standards of gcc/llvm handling of undefined behaviour): $ gcc test.c test.c:3:5: error: jump into statement expression
- ncmncm 6y agoPutty has coroutines coded exactly that way -- one huge file, with a case for each spot that does something that could block. A coworker said, "I love it, and I hate myself for loving it." Clang, and Gcc up to 8, will turn a switch statement with small numeric alternatives into a bitmask constant and a test against the bits. So, switch (c) case'a':case'e': case'i':case'o':case'u': return true; return false; turns into a range check and a "bt" instruction, effectively !!((1 << c-'a') & 0x40111) BUT: Gcc-9 and Gcc-10 both generate, instead, a jump table 168 bytes long. Microbenchmark results notwithstanding, this seems like a radical pessimization.