[HN Gopher] Weekend projects: getting silly with C
       ___________________________________________________________________
        
       Weekend projects: getting silly with C
        
       Author : nothacking_
       Score  : 211 points
       Date   : 2024-06-30 05:07 UTC (17 hours ago)
        
 (HTM) web link (lcamtuf.substack.com)
 (TXT) w3m dump (lcamtuf.substack.com)
        
       | mgaunard wrote:
       | aren't the switch shenanigans important to the duff's device?
        
         | tialaramex wrote:
         | Duff is relying on the fact you're allowed to intermingle the
         | switch block and the loop in K&R C's syntax, the (common at the
         | time but now generally frowned on or even prohibited in new
         | languages) choice to drop-through cases if you don't explicitly
         | break, and the related fact that C lets your loop jump back
         | inside the switch.
         | 
         | Duff is trying to optimise MMIO, you wouldn't do anything close
         | to this today even in C, not least because your MMIO is no
         | longer similarly fast to your CPU instruction pace and for non-
         | trivial amounts of data you have DMA (which Duff's hardware did
         | not). In a modern language you also wouldn't treat "MMIO" as
         | just pointer indirection, to make this stay working in C they
         | have kept adding hacks to the type system rather than say OK,
         | apparently this is an intrinsic, we should bake it into the
         | freestanding mode of the stdlib.
         | 
         | Edited to add:
         | 
         | For my money the successor to Tom Duff's "Device" is WUFFS'
         | "iterate loops" mechanism where you may specify how to
         | partially unroll N steps of the loop, promising that this has
         | equivalent results to running the main loop body N times but
         | potentially faster. This makes it _really easy_ for
         | vectorisation to see what you 're trying to do, while still
         | handling those annoying corner cases where M % N != 0 correctly
         | because that's the job of the tool, not the human.
        
           | uecker wrote:
           | Not sure what you mean by "hacks to the type system". All
           | modern computing essentially converged to unified memory,
           | which is exactly C's model.
        
             | tialaramex wrote:
             | While it's convenient _technically_ to have unified memory
             | and so it makes a lot of sense for your machine code, in
             | fact the MMIO isn 't just memory, and so to make this work
             | anyway in the C abstract machine they invented the
             | "volatile" qualifier. (I assume you weren't involved back
             | then?)
             | 
             | This should be a suite of intrinsics. It's the same mistake
             | as "register" storage, a layer violation, the actual
             | mechanics bleeding through into the abstract machine and
             | making an unholy mess.
             | 
             | If you had intrinsics it's obvious where the platform
             | specific behaviour lives. Can we "just" do unaligned 32-bit
             | stores to MMIO? Can we "just" write one bit of a hardware
             | register? It depends on your platform and so as an
             | intrinsic it's obvious how to reflect this, whereas for a
             | type qualifier we have no idea what the compiler did and
             | the ISO document of course has to be vague to be inclusive
             | of everybody.
        
               | uecker wrote:
               | I wasn't involved back then, but I know the history. I
               | thought you were talking about something more recent.
               | 
               | But this is all opinions and terms such as "unholy mess"
               | etc do not impress me. In my opinion "volatile" is just
               | fine as is "register. Neither are layer violations nor a
               | type system problem. That the exact semantics of a
               | volatile access are implementation defined seem natural.
               | How is this better with an intrinsic? What I would call a
               | mess are the atomics intrinsics, which - despite being
               | intrinsics - are entirely unsafe and dangerous and indeed
               | mess (just saw a couple of new bugs in our bug tracker).
        
               | tialaramex wrote:
               | Sure, it's just an opinion. I think the consequences
               | speak very well for themselves.
        
               | uecker wrote:
               | What consequences?
        
           | masklinn wrote:
           | > Duff is relying on the fact you're allowed to intermingle
           | the switch block and the loop
           | 
           | That's just a special case of being able to intermingle
           | switch with arbitrary syntax, which is what TFA does, before
           | it jumps to computed gotos.
        
             | doe_eyes wrote:
             | The overarching point appears to be getting rid of angle
             | brackets, which is not something that Duff is doing.
             | Further, Duff's device keeps case labels on the left of its
             | control structure; moving ifs to the left is the other
             | "innovation" here.
             | 
             | I think you really have to squint your eyes to see the
             | similarities, beyond the general theme of exploiting the
             | counterintuitive properties of switch statements.
        
       | smusamashah wrote:
       | Found these silly tricks by the author of this blog on twitter
       | first. Switch statement can do loops too
       | https://twitter.com/lcamtuf/status/1807129116980007037
        
         | viraptor wrote:
         | Also on the actually social network
         | https://infosec.exchange/@lcamtuf/112701486085621844
        
       | teo_zero wrote:
       | Another source of surprise:                 4[arr] // same as
       | arr[4]
        
         | stefanos82 wrote:
         | Thanks to array decay to pointer, we basically have
         | `*(array_label+offset)` which in this case of yours we have
         | `*(offset+array_label)`; in other words, `*(arr+4)` is the same
         | as `*(4+arr)`...that's it, really!
        
         | trealira wrote:
         | By the same principle, these are exactly the same:
         | arr[i][j]       j[i[arr]]
         | 
         | These are the simplifications you'd do. You only need to know
         | that a[x][y] is equivalent to (a[x])[y], and that a[x] is the
         | same as x[a].                 arr[i][j]       (arr[i])[j]
         | (i[arr])[j]       j[i[arr]]
        
       | geon wrote:
       | This can be used to implement coroutines in C.
       | https://stackoverflow.com/questions/24202890/switch-based-co...
        
         | emmericp wrote:
         | uIP (TCP/IP stack for tiny microcontrollers) is a another fun
         | real-world example for these types of coroutines:
         | https://github.com/adamdunkels/uip/blob/master/uip/lc-switch...
        
       | nxobject wrote:
       | If only there was a way of using setjmp/longjmp-style contexts
       | instead of goto, un/winding the stack as required. So we could
       | travel around in time... unfortunately you can't work with a
       | setjmp buffer before it's actually created, unlike gotos.
        
         | gpderetta wrote:
         | sigaltstack tricks to the rescue! (Although POSIX only, not ISO
         | C)
        
       | JohnMakin wrote:
       | My undergrad was entirely in the C language and I'm very glad for
       | it. Sometimes more modern languages can throw me for a loop, no
       | pun intended, but the beauty (and horror) of C is that you are
       | pretty close to the metal, it's not very abstracted at all, and
       | it allows you a lot of freedom (which is why it's so foot gunny).
       | 
       | I will never love anything as much as I love C, but C development
       | jobs lie in really weird fields I'm not interested in, and I'm
       | fairly certain I am not talented enough. I have seen C wizardry
       | up close that I know I simply cannot do. However, one of the more
       | useful exercises I ever did was implement basic things like a
       | file system, command line utilities like ls/mkdir etc. Sometimes
       | they are surprisingly complex, sometimes no.
       | 
       | After you program in C for a while certain conventions meant to
       | be _extra_ careful kind of bubble up in languages in a way that
       | seems weird to other people. for example I knew a guy that'd auto
       | reject C PR's if they didn't use the syntax if (1==x) rather than
       | if (x==1). The former will not compile if you accidentally use
       | variable assignment instead of equality operator (which everyone
       | has done at some point).
       | 
       | This tendency bites me a lot in some programming cultures, people
       | (ime) tend to find this style of programming as overly defensive.
        
         | smackeyacky wrote:
         | In an embedded environment, overly defensive is an asset
        
           | JohnMakin wrote:
           | That's precisely where my little professional C experience
           | was. I then switched to a python shop and was initially
           | horrified at some conventions, took some getting used to.
        
         | uecker wrote:
         | I force my students to do C development. And it turns out that
         | it is not that hard if you approach it with modern tools which
         | catch a lot of problems. The lack of abstraction is fixed with
         | good libraries.
         | 
         | C evolved a lot and many foot guns are not a problem anymore.
         | For example for
         | 
         | if (x = 1)
         | 
         | you nowaday get a warning. https://godbolt.org/z/79acPPro6
         | 
         | Implicit int, calling functions without prototypes, etc. are
         | hard errors. And so on.
        
           | tialaramex wrote:
           | The warning says to add parentheses, which sure enough
           | silences the warning, your foot, however, still has a bullet
           | hole in it.
        
             | uecker wrote:
             | The warning is very clear. If you did intend to use the
             | result of an assignment as truth value, you would notice.
             | In any case, did not have a single problem with this type
             | of error in the last decades, working with programmers of
             | various skill levels including beginners.
        
             | lelanthran wrote:
             | > The warning says to add parentheses, which sure enough
             | silences the warning, your foot, however, still has a
             | bullet hole in it.
             | 
             | The warning also says that it's an assignment. It's a
             | pretty clear warning meant to force the programmer to do
             | extra work to get the error.
        
           | Joel_Mckay wrote:
           | The libglib-dev with gcc is very handy for toy projects, but
           | only _after_ students try to write their own versions:
           | 
           | https://docs.gtk.org/glib/data-structures.html
           | 
           | It could be fun to do a lab summary after the lists and
           | hashes introduction.
           | 
           | Have a wonderful day, =)
        
             | uecker wrote:
             | I absolutely I agree that learning to create you own
             | abstractions is an incredible useful skill. It depends
             | though. For a programming course this makes absolutely
             | sense. But for applied problems in, say, biomedical
             | engineering, this does not work. Many students know only a
             | bit of Python, and then it is too much and "too
             | inconvenient" to start from scratch in C. With Python they
             | have a lot of things more easily available, so they make
             | quick progress. This does not lead to good results though!
             | For most of the Python projects, we end of throwing away
             | the code later. Another problem is that students often do
             | not know what they are doing, e.g. the use some statistical
             | package or visualization package and get nicely looking
             | results, but they do not know what it means and often it is
             | entirely wrong. For machine learning projects it is even
             | worse. So much nonsense and errors from copying other
             | people Python code....
        
               | Joel_Mckay wrote:
               | Python like Basic abstracted far to many details away
               | from students, and trying to convince people they need to
               | know how a CPU works later is nearly impossible.
               | 
               | In general, digging deep enough down a stack, and it
               | drops back into the gsl:
               | 
               | https://www.gnu.org/software/gsl/
               | 
               | Indeed, first month attrition rates for interns at some
               | companies is over 46%. =3
        
         | 8372049 wrote:
         | > if they didn't use the syntax if (1==x) rather than if
         | (x==1). The former will not compile if you accidentally use
         | variable assignment instead of equality operator
         | 
         | No need for Yoda notation. clang will warn of this by default
         | and gcc will do so if you compile with -Wall, which should also
         | be your default.
        
         | frou_dh wrote:
         | > for example I knew a guy that'd auto reject C PR's if they
         | didn't use the syntax if (1==x) rather than if (x==1). The
         | former will not compile if you accidentally use variable
         | assignment instead of equality operator
         | 
         | I've seen that one and personally dislike that mindset: Making
         | the code less readable to compensate for a disinterest in using
         | actual static analysis tooling.
        
           | tuveson wrote:
           | These days GCC and Clang will both give you warnings for this
           | if you have -Wall, which everyone should.
        
         | mgerdts wrote:
         | > I have seen C wizardry up close that I know I simply cannot
         | do.
         | 
         | I have written C at least a few times per year for over 30
         | years. About ten years of that was OS development on Solaris
         | and its derivatives.
         | 
         | Articles like this show crazy things you can do in C. I've
         | never found the need to do things like this and have never seen
         | them in the wild.
         | 
         | The places that wizardry is required are places like integer
         | and buffer overflow, locking, overall structure of large
         | codebases, build infrastructure, algorithms, etc. Many of these
         | are concerns in most languages.
         | 
         | > auto reject C PR's if they didn't use the syntax if (1==x)
         | rather than if (x==1)
         | 
         | When I was a student in the 90s advice like this would have
         | been helpful. Compiler warnings and static analyzers are so
         | much better now that tricks like this are not needed.
        
         | lelanthran wrote:
         | > I knew a guy that'd auto reject C PR's if they didn't use the
         | syntax if (1==x) rather than if (x==1). The former will not
         | compile if you accidentally use variable assignment instead of
         | equality operator (which everyone has done at some point).
         | 
         | That's not so much of a footgun anymore - the common C
         | compilers will warn you about that so there's not much point in
         | defending against it.
         | 
         | Same with literal format string parameters to printf functions:
         | the compiler is very good at warning about mismatched types.
        
       | JonChesterfield wrote:
       | This features the construct                 switch(k) {
       | if (0) case 0: x = 1;         if (0) case 1: x = 2;         if
       | (0) default: x = 3;       }
       | 
       | which is a switch where you don't have to write break at the end
       | of every clause.                 #define brkcase if (0) case
       | 
       | That might be worth using. Compilers won't love the control flow
       | but they'll probably delete it effectively.
        
         | leni536 wrote:
         | Surely the following would work just as well?
         | #define brkcase break;case
         | 
         | kinda defeats the purpose of the macro even.
        
           | MaxBarraclough wrote:
           | That strikes me as better. The original macro presumably
           | misbehaves if there's more than one statement in a sequence,
           | as the _if_ will only affect the first statement.
        
           | wrsh07 wrote:
           | I think the behavior is slightly different since this one
           | breaks the above case, and the other one only omits its case
           | from fallthrough
           | 
           | Incidentally, what happens if you use your brkcase as the
           | first case?
           | 
           | I don't find either particularly exciting - a macro that
           | would append break to the current case feels better
        
             | leni536 wrote:
             | Both version of the macro makes this fall through from 0:
             | switch (a) {         brkcase 0: foo();         case 1:
             | bar();       }
             | 
             | so in a sense the `if (0) case` trick also affects the
             | previous case, not the current one. But that one also falls
             | apart when there are multiple statements under the brkcase.
        
         | jppittma wrote:
         | I think it is super unclear how this works, and I would prefer
         | the same control flow using goto, rather than the duffs device
         | style switch abuses.
        
         | asveikau wrote:
         | It only works if the case label body is a single line or is
         | enclosed in brackets.
         | 
         | I'll confess, I've used this construct to mean "omit the first
         | line of the next case label but otherwise fall through".
         | 
         | If you think of the case label as merely a label and not a
         | delimiter between statements all of this makes sense.
        
       | fanf2 wrote:
       | see also https://www.chiark.greenend.org.uk/~sgtatham/mp/
       | 
       | Metaprogramming custom control structures in C by Simon Tatham
        
         | metadat wrote:
         | Discussed in July 2021 (43 comments):
         | 
         | https://news.ycombinator.com/item?id=27781784
        
       | quietbritishjim wrote:
       | > The above example will print the value of a, but it won't be
       | initialized to 123!
       | 
       | It certainly could do though. In C, using an uninitialised
       | variable does _not_ mean  "whatever that memory happened to have
       | in it before" (although that is a potential result). Instead,
       | it's undefined behaviour, so the compiler can do what it likes.
       | 
       | For example, it could well unconditionally initialise that memory
       | to 123. Alternatively, it could notice that the whole snippet has
       | undefined behaviour so simply replace it with no instructions, so
       | it doesn't print anything at all. It could even optimise away the
       | return that presumably follows that code in a function, so it
       | ends up crashing or doing something random. It could even
       | optimise away the instructions _before_ that snippet, if it can
       | prove that they would only be executed if followed by undefined
       | behaviour - essentially the undefined behaviour can travel back
       | in time!
        
         | uecker wrote:
         | UB can not travel back in time in C. Although it is true that
         | it can affect previous instructions, but that code is reordered
         | or transformed in complicated ways is true even without UB.
        
           | emmericp wrote:
           | The time-travelling UB interpretation was popularized by this
           | blog post about 10 years ago [1].
           | 
           | I'm not enough of a specification lawyer to say that this is
           | definitely true, but the reasoning and example given there
           | seems sound to me.
           | 
           | [1] https://devblogs.microsoft.com/oldnewthing/20140627-00/?p
           | =63...
        
             | uecker wrote:
             | Yes, random blog posts did a lot of damage here. Also
             | broken compilers [1]. Note that blog post is correct about
             | C++ but incorrectly assumes this is true for C as well.
             | 
             | [1]. https://developercommunity.visualstudio.com/t/Invalid-
             | optimi...
        
               | kibwen wrote:
               | I'm inclined to trust Raymond Chen and John Regehr on
               | these matters, so if you assert that they're incorrect
               | here then a source to back up your assertion would help
               | your argument.
        
               | uecker wrote:
               | I am a member of WG14. You should check the C standard. I
               | do not see how "time-travel" is a possible reading of the
               | definition of UB in C. We added another footnote to C23
               | to counter this idea:
               | 
               | https://www.open-
               | std.org/jtc1/sc22/wg14/www/docs/n3220.pdf "Any other
               | behavior during execution of a program is only affected
               | as a direct consequence of the concrete behavior that
               | occurs when encountering the erroneous or non portable
               | program construct or data. In particular, all observable
               | behavior (5.1.2.4) appears as specified in this document
               | when it happens before an operation with undefined
               | behavior in the execution of the program."
               | 
               | I should point out that compilers also generally do not
               | do true time-travel: Consider this example:
               | https://godbolt.org/z/rPG14rrbj
        
               | grumbelbart wrote:
               | So maybe we have different definitions of "time travel".
               | But I recall that
               | 
               | - if a compiler finds that condition A would lead to UB,
               | it can assume that A is never true - that fact can
               | "backpropagate" to, for example, eliminate comparisons
               | long before the UB.
               | 
               | Here is an older discussion:
               | https://softwareengineering.stackexchange.com/q/291548
               | 
               | Is that / will that no longer be true for C23? Or does
               | "time-travel" mean something else in this context?
        
               | singron wrote:
               | E.g. this godbolt: https://godbolt.org/z/eMYWzv8P8
               | 
               | There is unconditional use of a pointer b, which is UB if
               | b is null. However, there is an earlier branch that
               | checks if b is null. If we expected the UB to
               | "backpropagate", the compiler would eliminate that
               | branch, but both gcc and clang at O3 keep the branch.
               | 
               | However, both gcc and clang have rearranged the side
               | effects of that branch to become visible at the end of
               | the function. I.e. if b is null, it's as if that initial
               | branch never ran. You could observe the difference if you
               | trapped SIGSEGV. So even though the compiler didn't
               | attempt to "time-travel" the UB, in combination with
               | other allowed optimizations (reordering memory accesses),
               | it ended up with the same effect.
        
               | uecker wrote:
               | There may be different definitions, but also a lot of
               | incorrect information. Nothing changes with C23 except
               | that we added a note that clarifies that UB can not time-
               | travel. The semantic model in C only requires that
               | observable effects are preserved. Everything else can be
               | changed by the optimizer as long as it does not change
               | those observable effects (known as the "as if"
               | principle). This is generally the basis of most
               | optimizations. Thus, I call time-travel only when it
               | would affect previous _observable_ effects, and this what
               | is allowed for UB in C++ but not in C. Earlier non-
               | observable effects can be changed in any case and is
               | nothing speicifc to UB. So if you call time-travel also
               | certain optimization that do not affect earlier
               | observable behavior, then this was and is still allowed.
               | But the often repeated statement that a compiler can
               | assume that  "A is never true" does not follow (or only
               | in very limited sense) from the definition of UB in ISO C
               | (and never did), so one has to be more careful here. In
               | particular it is not possible to remove I/O before UB.
               | The following code has to print 0 when called with zero
               | and a compiler which would remove the I/O would not be
               | conforming.
               | 
               | int foo(int x)
               | 
               | {                 printf("%d\n", x);
               | fflush(stdout);            return 1 / x;
               | 
               | }
               | 
               | In the following example
               | 
               | int foo(int x)
               | 
               | {                 if (x) bar(x);            return 1 / x;
               | 
               | }
               | 
               | the compiler could indeed remove the "if" but not because
               | it were allowed to assume that x can never be zero, but
               | because 1 / 0 can have arbitrary behavior, so could also
               | call "bar()" and then it is called for zero and non-zero
               | x and the if condition could be removed (not that
               | compilers would do this)
        
               | lmm wrote:
               | While interactions with _volatile_ and interactive
               | streams cannot time-travel, anything else is free to -
               | the standard only imposes requirements on a conforming
               | implementation in terms of the contents of files at
               | program termination, and programs with undefined
               | behaviour are not required to terminate, so there are
               | approximately no requirements on a program that invokes
               | undefined behaviour.
        
               | quietbritishjim wrote:
               | > Also broken compilers [1].
               | 
               | The issue you linked to is not a counter example because,
               | as the poster said, g may terminate the program in which
               | case that snippet does not have undefined behaviour even
               | if b is zero. The fact that they bothered to mention that
               | g may terminate the program seems like an acknowledgement
               | that it would be valid to do that time travelling if it
               | didn't.
               | 
               | > Note that blog post is correct about C++ but
               | incorrectly assumes this is true for C as well.
               | 
               | Presumably you're referring to this line of the C++
               | standard, which does not appear in the C standard:
               | 
               | > However, if any such execution contains an undefined
               | operation, this International Standard places no
               | requirement on the implementation executing that program
               | with that input (not even with regard to operations
               | preceding the first undefined operation).
               | 
               | I looked at every instance of the word "undefined" in the
               | C standard and, granted, it definitely didn't have
               | anything quite so clear about time travel as that. But it
               | also didn't make any counter claims that operations
               | before are valid. It pretty much just said that undefined
               | behaviour causes behaviour that is undefined! So, without
               | strong evidence, it seem presumptuous to assume that
               | operations provably before undefined behaviour are well
               | defined.
        
               | uecker wrote:
               | The poster is me. You are right that this is not an
               | example for time-travel. There aren't really good
               | examples for true time travel because compilers generally
               | do not do this. But my point is that with compilers
               | behaving like this, people might confuse this for time-
               | traveling UB. I have certainly met some who did and the
               | blog posts seems to have similar examples (but I haven't
               | looked closely now).
               | 
               | Note that I am a member of WG14. We added more
               | clarification to C23 to make clear that this is not a
               | valid interpretation of UB, see here: https://www.open-
               | std.org/jtc1/sc22/wg14/www/docs/n3220.pdf
        
               | quietbritishjim wrote:
               | Ok, fair enough. I must admit I was looking at C99 as I
               | thought that was most generally relevant, I don't follow
               | recent C standards (as much as I do those for C++) and
               | C23 hasn't been ratified yet. I've found your new
               | snippet:
               | 
               | > In particular, all observable behavior (5.1.2.4)
               | appears as specified in this document when it happens
               | before an operation with undefined behavior in the
               | execution of the program.
               | 
               | I consider that a change in the standard but, of course,
               | that's allowed, especially as it's backwards compatible
               | for well defined programs.
               | 
               | The wording is a little odd: it makes it sound a like you
               | need some undefined behaviour in order to make the
               | operations beforehand work, and, taken very literally,
               | that operations between two undefined behaviours will
               | work (because they're still "before an operation with
               | undefined behavior"). But I suppose the intention is
               | clear.
        
               | uecker wrote:
               | The definition of UB (which hasn't changed) is:
               | "behavior, upon use of a nonportable or erroneous program
               | construct or of erroneous data, for which this document
               | imposes no requirement."
               | 
               | Note that the "for which" IMHO already makes this clear
               | that this can not travel in time. When everything could
               | be affected these words ("for which") would be
               | meaningless.
        
               | AlotOfReading wrote:
               | I didn't notice that section when I last read through
               | C23, but I'm very glad to see it. Reining in UB is one of
               | the hardest problems I've had to deal with, and being
               | able to say operations are defined up to the point of UB
               | makes my job so much easier.
               | 
               | The lack of clarity in earlier standards made it
               | impossible to deal with code incrementally, since all the
               | unknown execution paths could potentially breach back in
               | time and smash your semantics.
        
               | uecker wrote:
               | Thank you. This was my motivation. It is only a small
               | step... much more work to do.
        
           | ant6n wrote:
           | > but that code is reordered or transformed in complicated
           | ways is true even without UB.
           | 
           | Without undefined behavior, the compiler emits code that has
           | the behavior defined by the code --- the ordering may be
           | altered, but not the behavior.
        
             | uecker wrote:
             | Yes, and with undefined behavior, the compiler has to emit
             | code that has the behavior defined by the code up to the
             | operation that has undefined behavior.
        
       | jftuga wrote:
       | This reminds me of some silly C code I once wrote for fun, which
       | counts down from 10 to 1:                   #include <stdio.h> //
       | compile & run: gcc -Wall countdown.c -o countdown && ./countdown
       | int n = 10; int main(int argc, char *argv[]) { printf("%d\n", n)
       | && --n && main(n, NULL); }
       | 
       | Python version:                   import sys # run: python3
       | countdown.py 10         def main(n:int):
       | sys.stdout.write(f"{n}\n") and n-1 and main(n-1)
       | main(int(sys.argv[1]))
       | 
       | Shell version:                   # run ./countdown.sh 10
       | echo $1 && (($1-1)) && $0 $(($1-1))
        
         | cbrpnk wrote:
         | I don't think I've ever thought of explicitly calling main().
         | Made me chuckle.
        
           | akdev1l wrote:
           | I think it is UB
           | 
           | Edit: actually looks like it is UB in C++ but not C
        
             | colejohnson66 wrote:
             | Why would calling main be UB!? How is crt0 supposed to
             | work?
        
         | quietbritishjim wrote:
         | Nitpick: you could replace sys.stdout.write(f"{n}\n") with
         | print(n). The current code looks very much like it was written
         | for Python 2 (apart from the f string!), where print was a
         | statement. As of Python 3, print is just a regular function. It
         | returns None, which is falsey, so you'd also need to change
         | your first "and" to an "or".
        
       | pdimitar wrote:
       | _Fun at parties alert:_
       | 
       | Let's stop getting silly with C, too many CVEs!
       | 
       | ---
       | 
       |  _Serious comment:_
       | 
       | It's a rather cool article actually. Not something I'd do daily
       | but it's kind of sort of useful to know these techniques.
        
       | nj5rq wrote:
       | Why did I not know that this:                   case 1 ... 10:
       | 
       | Is valid C? I have been programming in C for years, what standard
       | is this from?
        
         | G4E wrote:
         | Unless it has been recently standardized it's not valid C, it's
         | a GNU extension.
        
         | dekhn wrote:
         | It appears to be a GNU C extension:
         | https://gcc.gnu.org/onlinedocs/gcc/Case-Ranges.html but I
         | couldn't find the history of the extension. I believe it is not
         | in standard C (not sure about clang).
        
           | nj5rq wrote:
           | I just tried it, and it works with clang version 17.0.6.
        
       | o11c wrote:
       | Due to the way lifetimes work in C (they begin with the block,
       | not the declaration), the following is legal:
       | #include <stdio.h>       #include <stddef.h>            int
       | main()       {           {               int *p = NULL;
       | if (p)               {               what:
       | printf("a = %d\n", *p);                   return 0;
       | }               int a = 123;               p = &a;
       | goto what;           }       }
        
       | junon wrote:
       | > switch (i) case 1: puts("i = 1");
       | 
       | I've seen this in the wild, particularly with macros.
       | #define assert(c) if (!c) ...                  if (foo)
       | assert(...);         else bar(); // oops!
        
       | codext wrote:
       | The final obfuscated code snippet in the article brought to light
       | another GCC extension:
       | 
       | https://stackoverflow.com/questions/34559705/ternary-conditi...
        
       ___________________________________________________________________
       (page generated 2024-06-30 23:01 UTC)