2

The following two problems I am having trouble with below are from chapter 5 exercise 3 in "C Programming a Modern Approach" by K.N. King.

1) i = 7; j = 8; k = 9;
   printf("%d ",(i = j) || (j = k));                           
   printf("%d %d %d\n", i, j, k);

    // answer I think it is 1 9 9 9
    // actual answer after running in C: 1 8 8 9

2) i = 1; j = 1; k = 1;
   printf("%d ", ++i || ++j && ++k);
   printf("%d %d %d\n", i, j, k);

   // answer I think it is 1 2 2 2
   // actual answer after running in C: 1 2 1 1


My questions are as follows:

  1. Why do the values for i and j in problem 1 not change after assignment in the first printf statement? Since the assignment operator expressions (i = j) and (j = k) are in parenthesis, they get evaluated first from right to left. At this point their values should be 9 and 9. I'm confused why the second printf statement outputs 8 and 8 for i and j?

  2. Why do the values for j and k in problem 2 not change after incrementing in the first printf statement? Since the prefix increments have higher precedence, they are evaluated first for i, j, and k from right to left. At this point their values should be 2, 2, and 2. Like problem 1, why does the second printf statement output 1 and 1 for j and k?

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
ChicoTabi
  • 45
  • 4
  • 5
    Read about the `&&` and `||` operators and about short-circuit evaluation. – Jabberwocky Apr 27 '23 at 10:14
  • 4
    "... they get evaluated first from right to left." no, why do you assume that? – Gerhardh Apr 27 '23 at 10:18
  • 2
    In your second example, also operator precedence is important. `A || B && C` is same as `A || (B && C)` where the expression in the brackets then is not evaluated due to short circuit evaluation – Gerhardh Apr 27 '23 at 10:21
  • 3
    BTW: You explicitely mention printf in your title. What you see is not related to printf or any function call. The rules for evaluation such an expression will also apply if you assign the value to a variable instead of passing it to printf – Gerhardh Apr 27 '23 at 10:23
  • 1
    @Gerhardh I'm not assuming it. Isn't the associativity of assignment from right to left? – ChicoTabi Apr 27 '23 at 10:26
  • 1
    True, but that only applies inside each bracket. It does not indicate that the right bracket would be evaluated first or that it is evaluated at all. That would be required to get the values `9` that you expect. – Gerhardh Apr 27 '23 at 10:27
  • 1
    @Gerhardh when you say inside each bracket, are you talking about the parenthesis? If so, if the assignment expressions are in parenthesis, they only right - left associate within the parenthesis? – ChicoTabi Apr 27 '23 at 10:34
  • 1
    Yes, I mean parentheses. If some sub-expression is enclosed in `()` then it is evaluated on its own. Assiciativity does not cross the boundary of that `()`. – Gerhardh Apr 27 '23 at 10:39
  • 1
    @Gerhardh for the second problem, I understand how short-circuit evaluation ignores operands if they aren't needed in order to solve the logical expression. However, because i, j, and k have the prefix increment, doesn't that mean that i, j, and k are incremented first due to higher precedence? Before the logical expressions are evaluated? Therefore, even if short-circuit evaluation is used, the values of i, j, and k are still incremented. – ChicoTabi Apr 27 '23 at 10:40
  • 1
    No, `++k` only means that the increment is done before evaluating its value. If short-circuit rule decide not to evaluate it at all, then also `++` is not evaluated. – Gerhardh Apr 27 '23 at 10:41
  • 1
    @Gerhardh so they don't all increment initially, even with higher precedence? – ChicoTabi Apr 27 '23 at 11:09
  • 2
    Precedence is not related to order of evaluation. Precedence defines what is grouped together. You might look at [Operator Precedence vs Order of Evaluation](https://stackoverflow.com/questions/5473107/operator-precedence-vs-order-of-evaluation) – Gerhardh Apr 27 '23 at 11:52
  • 2
    @Gerhardh Thank you for the help. That's what I seem to be confusing precedence and order of evaluation. So, precedence seems to only focus on grouping terms with parentheses, but not actually evaluating the expressions their associated with. – ChicoTabi Apr 27 '23 at 11:59
  • 4
    The sensible question to ask here is: "_why_ would you change the value of a variable inside of a printf statement in C?" A beginner asking that question has engineer potential. Because the answer to that is: "There is no reason why you would ever do such a stupid thing in real code". Best practices and programs written by truly skilled programmers strive to write source code _as simple and readable as possible_. Not as contrived and bug-prone as possible. – Lundin Apr 27 '23 at 13:14

4 Answers4

4

When you have

expr1 || expr2

expr1 will be evaluated first.

If the result of expr1 is true (aka non-zero), expr2will never be evaluated and the result of expr1 || expr2 will be 1.

If the result of expr1 is false (aka zero) expr2 will be evaluated. If the result of expr2 is non-zero, the result of expr1 || expr2 will be 1. Otherwise it will be zero.

So the line:

printf("%d ",(i = j) || (j = k));

can be rewritten as:

int tmp;
i = j;       // This assignment will always happen
if (i != 0)
{
    tmp = 1;
}
else
{
    j = k;  // This assignment may happen (depends on result of first assignment)
    if (j != 0)
    {
         tmp = 1;
    }
    else
    {
        tmp = 0;
    }
}
printf("%d ", tmp);

Use the same kind of thinking for printf("%d ", ++i || ++j && ++k); and you'll see why it's only i that changes.

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
3

First, operator precedence only controls the grouping of operators with operands. It does not affect order of evaluation.

Second, remember that both the || and && operators evaluate left-to-right and short-circuit. The left operand is evaluated first and any side effects are applied. Depending on the result (0 for &&, non-zero for ||) the right-hand operand will not be evaluated at all, and any side effects in the right-hand operand will not be applied.

In the statement

printf("%d ", (i = j) || (j = k));

the expression (i = j) is evaluated first, so i now has the value of j, which is 8. Since the result is non-zero, the expression (j = k) is not evaluated at all; the result of the expression is 1 and j is not updated (it stays 8).

In the statement

printf("%d ", ++i || ++j && ++k);

the expression ++i is evaluated first (so i now has the value 2); since the result is non-zero, ++j && ++k is not evaluated at all (operator precedence means the expression is parsed as ++i || (++j && ++k)) and neither j nor k are updated, so they both stay 1.

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

Let's consider the first code snippet

1) i = 7; j = 8; k = 9;
   printf("%d ",(i = j) || (j = k));                           
   printf("%d %d %d\n", i, j, k);

In the first call of printf

   printf("%d ",(i = j) || (j = k));                           

there is used an expression with the logical OR operator.

From the C Standard (6.5.14 Logical OR operator)

3 The || operator shall yield 1 if either of its operands compare unequal to 0; otherwise, it yields 0. The result has type int.

4 Unlike the bitwise | operator, the || operator guarantees left-to-right evaluation; if the second operand is evaluated, there is a sequence point between the evaluations of the first and second operands. If the first operand compares unequal to 0, the second operand is not evaluated.

As the left operand (i = j) of the expression is unequal to 0 then the right operand (j = k) is not evaluated.

So after the assignment (i = j) i becomes equal to j that is to 8. The result of the expression according to the quote from the C Standard is equal to 1.

So the first call of printf outputs the result of the expression with the logical OR operator that is equal to 1 and the next call of printf outputs variable i, j, k that are equal to 8, 8, 9.

Now let's consider the second code snippet

2) i = 1; j = 1; k = 1;
   printf("%d ", ++i || ++j && ++k);
   printf("%d %d %d\n", i, j, k);

In the first call of printf there is also used an expression with the logical OR and also with logical AND operators. The first call of printf can be rewritten like

   printf("%d ", ( ++i ) || ( ++j && ++k ));

Again as the operand ++i of the logical OR operator is not equal to 0 (its value after incrementing i is equal to 2) then the right operand of the logical OR operator ( ++j && ++k ) is not evaluated and the calls of printf output correspondingly 1, 2, 1, 1.

It is interesting to consider the first call or printf if to exchange operands of the logical OR operator the following way

   printf("%d ", ++j && ++k || ++i );

The used expression is equivalent to

   printf("%d ", ( ++j && ++k ) || ( ++i ) );

The first operand of the expression with the logical OR operator is an expression with the logical AND operator.

According to the C Standard (6.5.13 Logical AND operator)

3 The && operator shall yield 1 if both of its operands compare unequal to 0; otherwise, it yields 0. The result has type int.

4 Unlike the bitwise binary & operator, the && operator guarantees left-to-right evaluation; if the second operand is evaluated, there is a sequence point between the evaluations of the first and second operands. If the first operand compares equal to 0, the second operand is not evaluated.

In this case as the first operand ++j of the logical AND operator is not equal to 0 (the value of the expression equal to 2 after incrementing j) then the second operand ++k will be also evaluated and its value also is not equal to 0 (it is equal to 2 after incrementing k). So as the left operand ( ++j && ++k ) at this time of the logical OR operator is not equal to 0 then the right operand ++i will not be evaluated and as result the output will be 1, 1, 2, 2.

As a side note consider also the following code snippets

int i = 1;

printf( "%d\n", i++ || i++ || i++ );
printf( "%d\n", i );

The first call of printf will output 1 while the second call of printf will output 2.

And

int i = 1;

printf( "%d\n", i++ && i++ && i++ );
printf( "%d\n", i );

In this case the first call of printf also will output 1 (the result of the logical OR and of the logical AND operators is either 1 or 0 according to the provided quotes from the C Standard). And the second call or printf will output 4 because all operands of the expression with the logical AND operator will be evaluated.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
0
  • printf("%d ",(i = j) || (j = k)); is obfuscated crap to be replaced with:

    i = j;
    if(i > 0)
    {
      printf("%d ", i > 0);
    }
    else // i == 0
    {
      j=k;
      printf("%d ", j > 0);
    }
    
  • printf("%d ", ++i || ++j && ++k); is obfuscated crap to be replaced with:

    i++;
    if(i > 0)
    {
      printf("%d ", i > 0);
    }
    else // i == 0
    {
      j++;
      if(j > 0)
      {
        k++;
        printf("%d ", k > 0);
      }
      else
      {
        printf("%d ", j > 0);
      }
    }
    

What the author of the book wanted to you learn:

What we actually learnt:

  • There are many bad C books out there. Always use the KISS Principle. Or as was said by Brian Kernighan:

    Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.

  • Don't needlessly mix different operators in the same expression. Split complex expressions in several.

  • Always use parenthesis when the operator precedence and order of evaluation are not obvious. For example ++i || (++j && ++k) since it may not be obvious to everyone that && has higher precedence than ||. Not that it even mattered here.

Dangerous crap we have to unlearn after this exercise:

Lundin
  • 195,001
  • 40
  • 254
  • 396