The underlying point is that I think it *is* interesting to ask and to
answer why the OP gets the output that he gets. I also get that result
(using gcc on a Linux system) and, TBH, I can't explain it. Now, I know the
dogmatic position is that it doesn't matter what output you get, it doesn't
matter why you get the output you get, and it is a waste of time to even
think about thinking about why you get the output you get.
The point is that the newbie might not know that it's undefined (and may
also agree that it's a waste of time thinking about it once he knows
it is undefined).
The result is brought about by the same translation mechanism which brings
about well-defined results for well-defined code. That mechanism simply
But the C results defy logic (at least to me), and I'd be interested to find
out why this happens. Yes, Keith, we understand, it is all a waste of time,
but as noted above, we're here because the time is there to be wasted.
The C results are simply the compiler taking the liberties with regard
to function argument evaluation order. In the calling conventions on your
system, the leftmost argument goes to the top of the stack.
This means that, if push operations are used to prepare the arguments, the
rightmost argument value is pushed first.
(Even if it's not the case for non-variadic functions, you will find
that variadic function calls preserve this tradition because variadic
argument passing in C grew out of a hack which depended on the
arguments going onto the stack in that order.)
So in
a = 5;
printf("%d %d %d", a++, a++, a++)
the compiler wants to push the results in right to left order. If the object a
is reliably updated after each argument expression is evaluated, then we
get 7 6 5.
One implication of this kind of stack based passing arrangement where the
leftmost argument is at the top of the stack is that excess arguments to
functions do not matter.
printf("%d %d %d", 1, 2, 3, 4, 5);
The leftmost value is still at the top of the stack, where it is found by
the first %d. You will find that this is quite broadly portable.
Anyway, the problem with our test case is that even if there is a stack which
requires the arguments to be pushed from right to left, the evaluation doesn't
have to happen that way. The compiler could evaluate the three expressions in
a different order, save them in some temporary locations (or registers) and
then pass them in the required order. And of course, the side effects of
updating a can be deferred, wreaking havoc. We are onlly able to see 7 6 5
because the side effect updates are interleaved into the evaluation.
However, we can fix the test case so that it has unspecified behavior, rather
than undefined behavior, by using functions:
int fun(int *val)
{
return (*val)++;
}
/* ... */
a = 5;
printf("%d %d %d", fun(&a), fun(&a), fun(&a));
Now the increment is wrapped in sequence points inside the function.
Functions cannot be called in parallel in C, and so we have unspecified
behavior: there are exactly six possible evaluation orders for the three
function calls. We will see the numbers 5, 6 and 7 in the output, in one of
six possible orders.