News for hackers. Stuff that matters.
Re:How to Get Fired Using Switch Statements and Statement Ex... by userbinator on Friday October 28, 2016 @07:40AM (#12814852)
Statement expressions are certainly not in the standard nor supported by MSVC, and based on the compiler behaviour exhibited in the article, it looks like they may have been added in haste without considering (nor testing...) how the feature would interact with all the other features of the language.

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:

        movb    $1, %al
        testb   %al, %al
        jne     .LBB0_2
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:

        int i = 0;
        // switch(i)
        if(i == 0)
            goto case0;
        else
            goto switch_end;
        // i = 0;           // unreachable
        // goto loop_check; // unreachable
    loop_body:
        i;
        // loop increment
        i++;
    case0:
        // if there was code for case 0 it would go here
    loop_check:
        int r = 10; // result of statement expression
        if(i < r)
            goto loop_body;
    switch_end:
        return 0;
...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:

        int i = 0;
        if(i == 0)
            goto case0;
        else
            goto switch_end;
    case0:
        // if there was code for case 0 it would go here
        int r = 1;
        // continue with evaluating the parameter
        int p = i + r + i;
        // and call the function
        f(p);
    switch_end:
        return 0;
Re:How to Get Fired Using Switch Statements and Statement Ex... by jjnoakes on Friday October 28, 2016 @09:02AM (#12815289)
> 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 [...] and that does not cause any uninitialised reads or writes

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.

Re:How to Get Fired Using Switch Statements and Statement Ex... by yorwba on Friday October 28, 2016 @09:25AM (#12815504)
i is initialized twice in the example code

  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.
Re:How to Get Fired Using Switch Statements and Statement Ex... by jjnoakes on Friday October 28, 2016 @12:00PM (#12817405)
Yep, you are right, I missed that "i" was initialized twice; when I see "i=0" in a for loop I assume it isn't initialized right above (otherwise, why put the "i=0" in the for loop at all?).
Re:How to Get Fired Using Switch Statements and Statement Ex... by Hello71 on Friday October 28, 2016 @02:11PM (#12818986)
> not in the standard nor supported by MSVC

to be fair MSVC doesn't support a lot of things that are in the standard either

Re:How to Get Fired Using Switch Statements and Statement Ex... by wahern on Sunday October 30, 2016 @12:41AM (#12828201)
Statement expressions have been supported by GCC for almost two decades. Maybe more than two decades. Most C compilers support them, including clang, Sun Studio, Intel, and many others. In fact, MSVC is the only compiler I've used that doesn't support them.

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...