0

Going through other questions here, I've found that pre-increment operator in C returns rvalue, not lvalue. But, on trying the code below

int a=35;
printf("%d %d %d %d %d",a++,a,++a,a++,++a);

I expected the output

38 39 38 36 36

But I get the output

38 39 39 36 39

If pre-increment returns rvalue, then why is the final value of 'a' printed (implying lvalue is returned) instead of the value returned immediately after incrementing is performed?

MsPillai
  • 568
  • 1
  • 4
  • 7
  • 3
    God how many times should this question be answered? This is an undefined behaviour – Aswin Murugesh Feb 18 '14 at 09:01
  • 1
    See also: http://stackoverflow.com/questions/376278/parameter-evaluation-order-before-a-function-calling-in-c?lq=1 – P.P Feb 18 '14 at 09:02
  • 2
    Also, this has nothing to do with rvalues and lvalues. Simplified, lvalues are values that you can assign to, rvalues are values you can't assign to. You can't write `++a=5` because this is an rvalue. You're confusing lvalues and rvalues with left-to-right or right-to-left associativity. – Guntram Blohm Feb 18 '14 at 09:04
  • @AswinMurugesh But, doesn't printf() push arguments into a stack? In that case, the order of evaluation should be defined?? – MsPillai Feb 18 '14 at 09:18
  • @GuntramBlohm, No I assumed lvalue is returned because when i tried to trace out the order of evaluation from the output, there was no other possible explanation. – MsPillai Feb 18 '14 at 09:20
  • No, printf does not push the arguments on a stack. If anything, its the routine that calls printf which pushes arguments on the stack. But parameters might as well be passed in processor registers, which have no 'order' at all, and the order in which parameters are evaluated does not have to be the same as the order in which these evaluated parameters are pushed on the stack. The only thing you can rely on is that all parameters are evaluated before the function is called. Google about sequence points to learn more. – Guntram Blohm Feb 18 '14 at 09:33
  • 1
    "I assumed lvalue is returned because when i tried to trace out the order of evaluation": Again, rvalue/lvalue has NOTHING to do with order of evaluation. It does NOT mean left or right. – Guntram Blohm Feb 18 '14 at 09:38
  • gcc likes to reserve a buffer in advance for parameters in function calls, so it can evaluate the arguments in any order it chooses to. In addition, a compiler can apply side-effects anytime it chooses to as well: It can apply them between each argument, or only before/after all of them have been evaluated. – Medinoc Feb 18 '14 at 09:39
  • @AswinMurugesh: To be sure, the proposed original does not answer this question, because it does not state that evaluation of function arguments is unsequenced. – Eric Postpischil Feb 18 '14 at 15:09
  • @EricPostpischil I did propose another one, which addresses *evaluation of function arguments unspecified* part. Closing with multiple links isn't possible. While I agree with you that the wordings have certainly changed in C11, it's still UB based on the same rule that evaluating modifying an object more than once without an intervening sequence point is UB. If I interpret it correctly: *sequenced before*, *unsequenced* and *indeterminately sequenced* etc are more of a better definition to the existing rule rather than any real change to the rule itself. – P.P Feb 19 '14 at 20:25
  • @BlueMoon: If I recall correctly, and it is likely too much to hash out in comments, there are changes in effects, not just definition terms. For example, calling and returning from a function involves sequence points. So, if `f` performs some side effect, then there is a sequence point between those side effects in `f()+f()`, even though the order of those side effects is undetermined. The old definition would allow that (a sequence point is between them), while the new definition would make it undefined (they are not deterministically sequenced). – Eric Postpischil Feb 19 '14 at 20:38

2 Answers2

2

You are invoking undefined behavior: Commas separating function call parameters are not the "comma operator". And unlike it, they are not sequence points.

Also, your example has nothing to do with whether the operator return rvalue or lvalue, since your code doesn't attempt to modify the returned value.

Medinoc
  • 6,577
  • 20
  • 42
  • Since C 2011, C has based this undefined behavior on whether two side effects or a side effect and a value computation are *unsequenced* (5.1.2.3 3 and 6.5 2), not on whether there is an intervening sequence point. (An intervening sequence point is still sufficient but is not necessary.) – Eric Postpischil Feb 18 '14 at 15:13
  • @EricPostpischil You seem to imply that it's possible to prove *sequence point* & sequenced before (/after/unsequenced etc) can be proved independent of each other? More specifically, what are suggesting by this sentence: *An intervening sequence point is still sufficient but is not necessary*? Do you have any example for the opposite case? i.e. between two expressions A and B, there exists a strict partial ordering relation yet whether there's a sequence point or not can't be proven. – P.P Feb 19 '14 at 20:27
  • (First, a correction: My assertion that a sequence point is sufficient is likely wrong without additional conditions. The existence of an intervening sequence point is not always sufficient to determine order, since there are expressions in which a sequence point must occur between two things even though it is not specified which is first.) Yes, there are sequencings of expressions that do not involve sequence points. One is that the value computations of the operands of an operator are sequenced before the value computation of the result of the operator (6.5 1). – Eric Postpischil Feb 19 '14 at 20:42
1

When you write a function call such as printf("%d %d %d %d %d",a++,a,++a,a++,++a);, it turns into a set of operations to be performed:

  • The first argument evaluates to a pointer to the string.
  • The second argument results in two operations: Produce the value of a. Set a to the value of a incremented by one.
  • The third argument evaluates to the value of a.
  • The fourth argument results in two operations: Produce one more than the value of a. Set a to the value of a increment by one.
  • The fifth argument results in two operations: Produce the value of a. Set a to the value of a incremented by one.
  • The sixth argument results in two operations: Produce one more than the value of a. Set a to the value of a increment by one.
  • The function call passes the arguments to the function and calls it.

The C implementation has to perform all these operations. However, the C standard does not fully specify the order in which they are performed. Per C 2011 (N1570) 6.5.2.3 10, there is a sequence point after the evaluations of the arguments and before the call. This means that the operations resulting from the arguments themselves must be performed before the call is performed.

However, the C standard does not say in what order the arguments are evaluated. It does not even say that the two operations resulting from a single expression (as in a++) have to be performed together. The C implementation is allowed to prepare the value of a, then evaluate some other arguments, then later set a to the value of a incremented by one.

This means that your function call might be using the value of a and setting the value of a, multiple times, in any number of different orders. That is a problem because it violates a rule in the C standard. 6.5 2 says:

If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.…

In this passage, “unsequenced” means that two operations do not have a definite order set by the C standard: If a C implementation is allowed to perform two operations in either order, then they are unsequenced. Your printf call contains several unsequenced operations that have side effects on a (setting a is a side effect of a++) and that compute a value (simply using a to pass an argument “computes” its value).

Therefore, your printf call violates the rules of the C standard, and the resulting behavior is undefined.

Every normal expression you write should modify any object only once. Never use a++ twice in the same expression. Additionally, if you modify an object, you cannot separately use it. If you have a++ one place in an expression, you cannot have a separately in another place.

Some special operators have exceptions. The && and || operators are required to evaluate their left operands first. This causes subexpressions on the left to be sequenced before subexpressions on the right, and that avoids violating the rule in C 2011 6.5 2. Therefore a++ && a++ is legal. The comma operator also evaluates subexpressions on the left first, so a++, ++a, a is legal. However, arguments to a function call are a list of arguments, not a use of the comma operator.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312