Strange operands to conditional operator

Discussion in 'C Programming' started by paulo, Nov 14, 2006.

  1. paulo

    paulo Guest

    Can anyone please tell me how the C language interprets the following
    code:

    #include <stdio.h>

    int main(void)
    {
    int a = 1;
    int b = 10;
    int x = 3;
    int r;

    r = (x > 0) ? a = b : 0; /* what does this statement mean */

    printf("%d\n", r);

    return r;
    }

    The statement containing the conditional operator is the puzzle.
    If it was written as "r = (x > 0) ? (a = b) : 0;" there wouldn't be a
    problem.
    However, according to the language standard, the conditional operator
    has higher precedence than an assignment operator, so the assignment of
    b to a should not be performed until after the conditional operator has
    been evaluated. So what value *should* be assigned to "r" - or is the
    code meaningless ?

    Thank you

    Paulo
     
    paulo, Nov 14, 2006
    #1
    1. Advertising

  2. paulo

    Guest

    paulo wrote:
    > Can anyone please tell me how the C language interprets the following
    > code:
    >
    > #include <stdio.h>
    >
    > int main(void)
    > {
    > int a = 1;
    > int b = 10;
    > int x = 3;
    > int r;
    >
    > r = (x > 0) ? a = b : 0; /* what does this statement mean */
    >
    > printf("%d\n", r);
    >
    > return r;
    > }
    >
    > The statement containing the conditional operator is the puzzle.
    > If it was written as "r = (x > 0) ? (a = b) : 0;" there wouldn't be a
    > problem.
    > However, according to the language standard, the conditional operator
    > has higher precedence than an assignment operator, so the assignment of
    > b to a should not be performed until after the conditional operator has
    > been evaluated. So what value *should* be assigned to "r" - or is the
    > code meaningless ?


    The order of precedence is not relevant to the question, IMHO (I'm
    prepared to be shown wrong, of course).

    The tertiary conditional operator (which I dislike, personally) must
    take the form "expr1?expr2:expr3".

    If expr2 contains an assignment, that's not going to wait until after
    the condtional has been evaluated - the conditional can't be evaluated
    until the the assignment takes place.

    So after the line in question, "a" contains 10, as does "r".
     
    , Nov 14, 2006
    #2
    1. Advertising

  3. paulo

    Richard Bos Guest

    "paulo" <> wrote:

    > Can anyone please tell me how the C language interprets the following
    > code:


    > r = (x > 0) ? a = b : 0; /* what does this statement mean */


    > The statement containing the conditional operator is the puzzle.
    > If it was written as "r = (x > 0) ? (a = b) : 0;" there wouldn't be a
    > problem.
    > However, according to the language standard, the conditional operator
    > has higher precedence than an assignment operator,


    This is misleading...

    > so the assignment of b to a should not be performed until after the
    > conditional operator has been evaluated.


    ....and this is, in any case, not a correct conclusion to draw from _any_
    question of preference in C.


    There is, strictly speaking, no precedence in C. There is a syntax,
    which specifies what an expression can look like. If you take all the
    rules which specify the various kinds of expression, you end up with
    something which looks suspiciously like the traditional precedence
    table, but this table is a simplification of the real rules.

    In the case of the above statement, the rule for a conditional
    expression is:

    conditional-expression:
    logical-OR-expression
    logical-OR-expression ? expression : conditional-expression

    and that for an assignment expression is:

    assignment-expression:
    conditional-expression
    unary-expression assignment-operator assignment-expression

    Add these together, and you'll see that in

    a = b ? c : d;

    this must parse as an assignment expression, of which the right-hand
    part is also an assignment expression, which falls through to a
    conditional expression. IOW, this parses as

    a = (b ? c : d);

    OTOH, in your case, which schematically is

    a ? b = c : d;

    the parse for the assignment expression cannot involve the : from the
    surrounding conditional expression, because there is nothing in the
    grammar which _can_ produce such a half-conditional. The only possible
    parse is

    a ? (b = c) : d;

    This is complicated by this whole expression being inside another
    assignment expression, but when you go through the grammar, you must end
    up with the parse you quoted above yourself: the expression without the
    extra parens is required to be parsed as the one with the parens. More
    precisely yet, the statement

    r = (x > 0) ? a = b : 0;

    parses like

    r = ((x>0) ? (a=b) : 0);


    Now for the second part: order of evaluation. The order of evaluation of
    sub-expressions in a C expression is normally free. In the expression
    a+b, the implementation is free to fetch a first, b first, both in
    parallel, or however it chooses; as long as the addition is performed
    correctly, it does not matter.
    This can be a trap for the unwary: for example, in push(pop() - pop()),
    there is no guarantee which of the two pop()s is called first, and
    therefore no guarantee that the number which is pushed is the top of the
    stack minus the second stack member, or vice versa. Similarly, in
    func1() + func2()*func3(), the fact that * binds more closely than +
    does not mean that func2() and func3() must always be called before
    func1(). Nor, of course, the reverse.

    There is one thing, though, which is an exception to this freedom, and
    that is sequence points. There are certain spots in a C program at which
    every computation and side-effect must[1] be finished, and the next set
    started. The obvious one is the end of a full expression, e.g. the end
    of a whole expression statement, or the controlling expression of an if
    statement; others are just before a function is called, after its
    arguments have been evaluated (but _not_ between two argument
    evaluations), just before a library function returns, and after the
    first operand of certain operators.
    That last one is significant, here. One of these operators is &&; there
    is a sequence point between the first and second operand of any &&
    operation. This is useful, for example, when you do if (ptr && *ptr<x),
    because it means that the program must first evaluate ptr; the
    definition of && also says that if ptr is 0, *ptr won't be evaluated,
    and you won't have a segfault.[2] There is a similar rule for the
    logical or operator, and for the comma.
    You can see it coming: yes, the same rule goes for the ?: operator.
    There is a sequence point after the first operand. This, added with the
    rest of the definition of ?:, means that this (or something with the
    exact same effect) happens:
    - _First_, the first operand is evaluated. In your case, x is compared
    to 0.
    - Then, the decision is made which of the two other operands to
    evaluate.
    - And only then is _either_ the second _or_ the third operand evaluated.
    Never both. And whichever is evaluated, that's only done after the
    first operand is finished.

    However, this is because ?:, like &&, || and , is special, and has an
    extra sequence point and a rule about the order in which the operands
    are evaluated. It is not because of precedence. This doesn't hold true
    with operators like * and +, which also have different "precedence", but
    do not have the special sequence point rules.


    In toto, the statement

    r = (x > 0) ? a = b : 0;

    means this:

    - Evaluate (x>0).
    - If x>0, assign the value of b to both a and r. Note that there is no
    sequence point between the _second_ operand of ?: and its surrounding
    expression (here, the assignment to r), so the order in which b gets
    assigned to a and r is unspecified.
    - If x<0, don't touch either a or b, and assign 0 to r.

    Richard

    [1] FSVO "must". If the compiler can prove that the program cannot or
    will not detect the difference, it can shuffle as it likes. If it
    cannot prove this, however, it must adhere to the prescribed order.
    [2] Again, if it can be proved that you can't tell the difference - for
    example, if the compiler knows that this OS doesn't have memory
    protection, and reading from a null pointer doesn't do anything, it
    can ignore this rule. But _only_ if it makes no difference.
     
    Richard Bos, Nov 14, 2006
    #3
  4. paulo:

    > r = (x > 0) ? a = b : 0; /* what does this statement mean */



    More verbosely, it can be written as:

    if (x > 0)
    {
    a = b;

    r = a;
    }
    else
    {
    r = 0;
    }


    > However, according to the language standard, the conditional operator
    > has higher precedence than an assignment operator, so the assignment of
    > b to a should not be performed until after the conditional operator has
    > been evaluated. So what value *should* be assigned to "r" - or is the
    > code meaningless ?



    Recently enough, someone posted a link to a post by Chris Torek (I believe,
    and apologies if I misspelled the name) which was very helpful in explaining
    operator evaluation and so forth.

    --

    Frederick Gotham
     
    Frederick Gotham, Nov 14, 2006
    #4
  5. On Tue, 14 Nov 2006, Richard Bos wrote:
    > "paulo" <> wrote:
    >>
    >> Can anyone please tell me how the C language interprets the following
    >> code:

    >
    >> r = (x > 0) ? a = b : 0; /* what does this statement mean */

    [big snip]
    > In toto, the statement
    >
    > r = (x > 0) ? a = b : 0;
    >
    > means this:
    >
    > - Evaluate (x>0).
    > - If x>0, assign the value of b to both a and r. Note that there is no
    > sequence point between the _second_ operand of ?: and its surrounding
    > expression (here, the assignment to r), so the order in which b gets
    > assigned to a and r is unspecified.


    I snipped most of an excellent explanation! However, you might have
    gone a little overboard right here. At least, IMHO, you ought to have
    added that it's not the value of 'b' that gets assigned to 'r' --- it's
    the value of 'a', or possibly "the value of 'b', converted to a value
    of the same type as 'a'".

    If b,a,r are all of the same type, it doesn't really matter (AFAICT),
    but if the types are respectively int, unsigned char, int, then it
    could matter.

    > - If x<0, don't touch either a or b, and assign 0 to r.


    -Arthur
     
    Arthur J. O'Dwyer, Nov 14, 2006
    #5
  6. "paulo" <> writes:
    > Can anyone please tell me how the C language interprets the following
    > code:
    >
    > #include <stdio.h>
    >
    > int main(void)
    > {
    > int a = 1;
    > int b = 10;
    > int x = 3;
    > int r;
    >
    > r = (x > 0) ? a = b : 0; /* what does this statement mean */
    >
    > printf("%d\n", r);
    >
    > return r;
    > }

    [...]

    Others have explained how the language rules apply to this code. I'll
    make another point: this is bad code.

    As a puzzle, there's nothing wrong with it, but I'd never write
    something like that in production code. "a", "b", "x" and "r" are, in
    most contexts, lousy variable names. (But "x" could be acceptable as
    a parameter name for a mathematical function, and "r" might mean
    "radius" if it's sufficiently clear from context.) Embedding
    assignments within larger expressions is perfectly legal, but can lead
    to confusion. The associations of operands with operators is not
    ambiguous, but there's no good reason not to add parentheses to make
    it clearer, so a reader doesn't have to consult the language grammar
    to understand the code. (The compiler effectively does so, but that's
    its job.)

    r = ((x > 0) ? (a = b) : 0);

    But I wouldn't use the "?:" operator at all here:

    if (x > 0) {
    r = a = b; /* chained assignments are clear enough, IMHO */
    }
    else {
    r = 0;
    }

    And as long as we're simplifying things, the whole program reduces to:

    #include <stdio.h>
    int main(void)
    {
    puts("10");
    return 10;
    }

    Finally, returning 10 from main() could have unpredictable results;
    the only portable return values are 0, EXIT_SUCCESS, and EXIT_FAILURE.

    Again, there's nothing wrong with the original code *as a puzzle*, and
    there's nothing wrong with puzzles. Solving such puzzles is one of
    the many things a good C programmer needs to know how to do (since we
    all occasionally need to understand code written by someone who
    thought he was "clever".) But another important skill is knowing not
    to write such code in the first place.

    --
    Keith Thompson (The_Other_Keith) <http://www.ghoti.net/~kst>
    San Diego Supercomputer Center <*> <http://users.sdsc.edu/~kst>
    We must do something. This is something. Therefore, we must do this.
     
    Keith Thompson, Nov 14, 2006
    #6
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Chao
    Replies:
    6
    Views:
    1,310
    Mike Treseler
    Jul 5, 2004
  2. A. Kong
    Replies:
    3
    Views:
    17,870
    Alan Fitch
    Oct 11, 2004
  3. Jerry Krinock
    Replies:
    6
    Views:
    373
    Jerry Krinock
    Feb 18, 2005
  4. Frank Buss
    Replies:
    5
    Views:
    10,549
    Duane Clark
    Jul 1, 2006
  5. JoseMariaSola

    Is cast operator unary or binary? How many operands?

    JoseMariaSola, Apr 29, 2008, in forum: C Programming
    Replies:
    16
    Views:
    1,245
    David Thompson
    May 12, 2008
Loading...

Share This Page