The code you wrote did one additional thing: it moved the "switch" starting point from before the "i=0" initialization to after the "i=0" initialization, which is more than just a different interpretation, it's completely different semantics.
int i = 0;
switch(i){
for(i = 0; i < ({case 0:; 10;}); i++){
The compiler probably tried to be intelligent and optimized it away.EDIT: Okay I tried reading the assembly and I think I understand whats going on:
movl $0, -8(%rbp) # translation of i = 0
... # the following is jumped over
movl -8(%rbp), %eax
movl %eax, -16(%rbp) # 4-byte Spill # i is moved to somewhere else in memory
... # the switch jumps between these
movl -16(%rbp), %eax # 4-byte Reload # tries to reload i from its "new" position
Whoever wrote the code to place variables into memory obviously didn't consider this kind of insane control flow.to be fair MSVC doesn't support a lot of things that are in the standard either
As for whether they were well thought out, consider that you have the same initialization scoping problem with C99's compound literals, and more generally with any automatic-storage variables. You cannot jump over (using goto or switch) the declaration or definition statement that instantiates the variable. It's doubly stupid when you do it with statement expressions because the warning is right there in the very small, concise section of the GCC manual explaining them. And nobody should be using a construct without understanding the rules for using it, which typically means reading the rules.
You can't fix stupid.
I was actually bit by a scoping issue like this a year or two ago. I was using a compound literal in some code that a drive-by patch submitter made work with C++. Now, I'm not a C++ programmer, but I knew that C++ didn't support C99 compound literals. Yet I stupidly assumed that because GCC and clang accepted the construct syntactically (and without warning) they'd implemented the same semantics. Well, they didn't, at least not after somebody "fixed" those compilers to give that syntax the semantics of C++ temporaries. (Which made no sense to me; it's a parse error in standard C++, and subtly changing the semantics like that was bound to break code.) The difference was important, because compound literals have block-scoped lifetimes, whereas C++ temporary objects only have expression-scoped lifetimes. In short, this caused perfectly legal C code to scribble over stack memory when compiled as C++.
I must assume at least some of the blame because I should have known better than to continue using a compound literal in header code that purported to support C++. OTOH, I had zero intention of ever using that code as C++; I just thought I was doing somebody a favor by accepting their patch. No favor goes unpunished, I guess.
As for Duff's Device... I love using the Duff's Device to implement a poor man's coroutine in standard C. You can write very clean and concise code this way. For example, here's a re-entrant implementation of POSIX getopt I wrote. It's probably the shortest correct implementation of getopt in C or most any other language.
https://github.com/wahern/lunix/blob/rel-20161026/src/unix-g...
The Asm fragment there seems like it doesn't even match the C at all; it begins with these 3 rather useless instructions which appear to be a very dumb translation of "if((char)1)", a statement that does not even exist in the C code:
In the first iteration the use of the jump from the switch statement jumps over these instructions, although they are executed on later loop iterations. It seems pretty reasonable that the compiler would do this, after all you are telling it to branch into the middle of a for loop comparison.IMHO if branching to that location is allowed, this would be a defect because there is a (slightly) saner interpretation: branch there to execute the case, then "return" 10 from the statement expression and compare that to i, continuing onward from there as usual. Whether the compiler chooses to use temporary variables shouldn't make any difference according to these semantics. If we "linearise" that example according to such a principle, it may become something like this:
...and that does not cause any uninitialised reads or writes. Also, the extremely contorted control flow in some of these examples reminds me of continuations.The jump into the function parameter is puzzling at first (and once again shows what I'd consider a defect since it crashes), but using the same semantics of "continue at the remainder of the statement", it can be transformed into this: