Can you change the value of a variable inside of a printf statement in C?

130 views Asked by At

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?

4

There are 4 answers

0
Support Ukraine On

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.

0
Vlad from Moscow On

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.

0
John Bode On

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.

0
Lundin On
  • 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: