0

Edit:

What is the real groups of the expression “3<8 ? (9<6 ? 7 : 5) : 2>0 ? 4 : 1” and the meaning of non-associative in PHP?

has been offered as a duplicate, but that concerns PHP, not C.

While building some test cases for a small program, I introduced a bug into the conditional part of a for loop, like this:

for(int row = 0; row < (mode == TEST) ? NO_OF_TESTS : ROWS; row++){}

(I know one should pull that out of the for loop, but that does not change the problem.)

This lead to a segmentation fault by overrunning the end of an array as the above results in an infinite loop.

Of course, the fix was easy enough:

for(row = 0; row < ((mode == TEST) ? NO_OF_TESTS : ROWS); row++)
//                 ^                                   ^

But I am more interested in the way the faulty implementation behaved.

Here is a complete piece of code (does not make sense in itself, as it is ripped out of context, but it demonstrates the problem).

#include <stdio.h>
#include <stdlib.h>

#define TEST 0
#define INTERACTIVE 1

#define ROWS 2
#define NO_OF_TESTS 3
#define MAX_FRUIT_LEN 50

int main(void)
{
    char test_cases[NO_OF_TESTS][MAX_FRUIT_LEN] =
    {{"Orange"},
     {"Apple"},
     {"Pineapple"}};

    int mode = TEST;
    int row = 0;

    //This fails - but in a strange way
    //Uncomment this `for` loop and comment the other one to see the effects

    //for(int row = 0; row < (mode == TEST) ? NO_OF_TESTS : ROWS; row++)

    //With the parantheses, obviously, it works.
    for(row = 0; row < ((mode == TEST) ? NO_OF_TESTS : ROWS); row++)
    {
        printf("Working:\tIn row %d: Mode: %d condition_eval: %d\n"
        , row , mode, row < ((mode == TEST) ? NO_OF_TESTS : ROWS));

        printf("Not Working:\tIn row %d: Mode: %d condition_eval: %d\n"
        , row, mode, row < (mode == TEST) ? NO_OF_TESTS : ROWS);

        printf("Row: %d \tFruit Name: %s\n",row, test_cases[row]);
    }
    printf("\nTerminating conditional evaluation (at row %d):\n", row);

    printf("Working:\tIn row %d: Mode: %d condition_eval: %d\n"
    , row , mode, row < ((mode == TEST) ? NO_OF_TESTS : ROWS));

    printf("Not Working:\tIn row %d: Mode: %d condition_eval: %d\n"
    , row, mode, row < (mode == TEST) ? NO_OF_TESTS : ROWS);

    return 0;
}

Looking at the output and the (wrong) conditional

row < (mode == TEST) ? NO_OF_TESTS : ROWS

it appears that the compiler interprets this as:

   (row < (mode == TEST)) ? NO_OF_TESTS : ROWS
// ^                    ^

The question is: Why?

This expression:

(mode == TEST)

could be interpreted as either being the right operand to the < operator, or as the left operand to the ? operator. (But not both at the same time, I guess.)

Which rules apply? Is it a matter of operator precedence? Do sequence points play a role? What is the order of evaluation, and why?

I'm quite confused; any help is greatly appreciated.

GermanNerd
  • 643
  • 5
  • 12
  • 2
    @WernerHenze: Don't think about linking a PHP dupe to a C question. The PHP guys decided to reverse the associativity of the ternary conditional operator! – Bathsheba Jan 10 '19 at 12:08
  • Instead of writing icky operator goo inside the controlling clause of a for loop, write readable code: `int max = something; for(int row = 0; row – Lundin Jan 10 '19 at 12:17
  • "*Is it a matter of operator precedence?*" Why not just look this up in any C book? For example here: https://en.cppreference.com/w/c/language/operator_precedence – alk Jan 10 '19 at 12:22
  • @Lundin True. I actually wrote that inside my original post. Didn't change the code since it has not any impact on the problem. – GermanNerd Jan 10 '19 at 12:33
  • @Bathsheba Good point. Then let's take this one: https://stackoverflow.com/questions/25598884/conditional-operator-unary-in-c/25598932#25598932 – Werner Henze Jan 10 '19 at 13:56

3 Answers3

3

The ternary conditional operator has a low precedence.

So

row < (mode == TEST) ? NO_OF_TESTS : ROWS

is grouped as

(row < (mode == TEST)) ? NO_OF_TESTS : ROWS

Folk like to think in terms of operator precedence tables, but really the groupings are hardwired into the language grammar.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • Could you elaborate on the groupings? What determines those? (A am aware of the higher precedence of the `<` operator.) – GermanNerd Jan 10 '19 at 12:08
  • @GermanNerd: The language grammar does. The C grammar is reasonably simple - a good (but not particularly current) reference is at the back of K & R. – Bathsheba Jan 10 '19 at 12:09
  • I actually did look there - K&R mentions groupings only in passing. It appears to me that precedence actually is not the issue here, but only grouping. Could you point me to a section of either K&R or the standard where grouping rules are laid down? K&R doesn't even list it in its index. – GermanNerd Jan 10 '19 at 12:36
  • Trust me; it's in the grammar. For a simpler one, have a look at how `*` comes before `+`. Did you study EBNF at school? – Bathsheba Jan 10 '19 at 12:38
  • Although it's a C++ Q&A, one answer where I described how operator precedence and associativity is a result of the language grammar rules: https://stackoverflow.com/a/46342170/459640 – aschepler Jan 10 '19 at 12:48
  • @Bathsheba No, I guess EBNF was too new back when I was in school...;) Now, if grouping on the comparison operators is left-to-right (as I remember), what is the rule for determining how much of any right-side expression is "pulled in"? Or to put it differently: What determines where the (implicit) parans are placed, when there are at least two legal possibilities of placing explicit ones? – GermanNerd Jan 10 '19 at 12:48
  • @GermanNerd That would make a good followup question. (Just be sure to specify an answer from the Standard, and not from the commonly used operator precedence tables, which don't actually appear anywhere in the C or C++ Standards.) – aschepler Jan 10 '19 at 12:57
  • @aschepler Thanks for the link, somewhat insightful. Could it be that the question of grouping is the result of which operator binds more tightly? – GermanNerd Jan 10 '19 at 13:00
  • @GermanNerd: Sorry to keep trolling your questions, but it's the other way round: the operator binds more tightly *because* of the grammar. – Bathsheba Jan 10 '19 at 13:01
  • 1
    @Bathsheba No, maximal munch is about dividing source characters into preprocessor tokens. It has nothing to do with determining subexpressions of expressions. – aschepler Jan 10 '19 at 13:01
  • "An operator binds more tightly" is an informal way of describing how subexpressions are combined in a conceptual tree shape into longer expressions. – aschepler Jan 10 '19 at 13:04
  • @aschepler: You may well be correct, but it's how I mentally think of 1 + 1 + 1.0 being the addition of 1 + 1 to an int, then converted to 2.0 (double) followed by adding 1.0. Perhaps I should ask that question. You are clearly the expert here. – Bathsheba Jan 10 '19 at 13:04
  • 1
    @Bathsheba That basically works for binary operators of the same precedence and all left associative. But clearly in `1 + 1 * 1.0`, the `+` can't "munch" the following `1`. And since `=` is right-associative, the first `=` in `a = b = c` can't munch the `b` either. – aschepler Jan 10 '19 at 13:07
  • ... and "associativity" is also a result forced by the grammar setup. – aschepler Jan 10 '19 at 13:09
  • @GermanNerd: aschepler is the expert here; ignore any of my comments that are in contradiction to theirs (although I'll keep them up else the comments will not read as well). – Bathsheba Jan 10 '19 at 13:10
  • @Bathsheba No, please keep discussing. It is very enlightening. – GermanNerd Jan 10 '19 at 13:11
  • @aschepler Maybe a question in itself: Is it a useful perspective to regard the expression `(mode == TEST)` as being in contest of ownership by the two operators in the complete expression, and therefore the operator with the higher precedence "grabs" it? – GermanNerd Jan 10 '19 at 13:14
  • @GermanNerd It is a useful perspective as a convenient way to remember and understand what ends up happening. And very often that's good enough, so the answers to this question are all correct in that sense. It's just not an accurate description of precisely how the Standard defines via a recursive grammar what the subexpression operands of each operator are. – aschepler Jan 10 '19 at 22:33
0

The question is: Why?

Is it a matter of operator precedence ?

yes !

The precedence of the ternary operator is quite dangerous, you are not the first doing that error, I did myself

The best way to be quiet in expressions is to add the ()

Community
  • 1
  • 1
bruno
  • 32,421
  • 7
  • 25
  • 37
0

Yes, it is a matter of precedence - relational operators (as well as bitwise, logical, arithmetic, unary, and postfix operators) have higher precedence than the ternary operator, so the expression a < b ? c : d is parsed as (a < b) ? c : d.

Same if the leftmost expression is a && b, a == b, a * b, etc.

John Bode
  • 119,563
  • 19
  • 122
  • 198