I'm not going to explain the whole program; I'm going to focus on the "WHAT IS HAPPENING HERE" line. I think we can agree that before this line, the v[]
array looks like this, with a
pointing at v
's last element:
+----+----+----+----+
v: | 20 | 35 | 75 | 80 |
+----+----+----+----+
0 1 2 3
^
+-|-+
a: | * |
+---+
Now, we have
*a-- = *a+1;
It looks like this is going to assign something to where a
points, and decrement a
. So it looks like it will assign something to v[3]
, but leave a
pointing at v[2]
.
And the value that gets assigned will evidently be the value that a
points to, plus 1.
But the key question is, when we take *a+1
on the right-hand side, will it use the old or the new value of a
, before or after the decrement on the right-hand side? It turns out this is a really, really hard question to answer.
If we take the value after the decrement, it'll be a[2]
, plus 1, or 76 that gets assigned to a[3]
. It looks like that's how your compiler interpreted it. And this makes a certain amount of sense, because when we read from left to right, it's easy to imagine that by the time we get around to computing *a+1
, the a--
has already happened.
Or, if we took the value before the decrement, it would be a[3]
, plus 1, or 81 that gets assigned to a[3]
. And that's how it was interpreted by three different compilers I tried it on. And this makes a certain amount of sense, too, because of course assignments actually proceed from right to left, so it's easy to imagine that *a+1
happens before the a--
on the left-hand side.
So which compiler is correct, yours or mine, and which is wrong? This is where the answer gets a little strange, and/or surprising. The answer is that neither compiler is wrong. This is because it turns out that it's not just really hard to decide what should happen here, it is (by definition) impossible to figure out what happens here. The C standard does not define how this expression should behave. In fact, it goes one farther than not defining how this expression should behave: the C Standard explicitly says that this expression is undefined. So your compiler is right to put 76 in v[3]
, and my compilers are right to put 81. And since "undefined behavior" means that anything can happen, it wouldn't be wrong for a compiler to arrange to put some other number into v[3]
, or to end up assigning to something other than v[3]
.
So the other part of the answer is that you must not write code like this. You must not depend on undefined behavior. It will do different things under different compilers. It may do something completely unpredictable. It is impossible to understand, maintain, or explain.
It's pretty easy to detect when an expression is undefined due to order-of-evaluation ambiguity. There are two cases: (1) the same variable gets modified twice, as in x++ + x++
. (2) The same variable gets modified in one place, and used in another, as in *a-- = *a+1
.
It's worth noting that one of the three compilers I used said "eo.c:15: warning: unsequenced modification and access to 'a'", and another said "eo.c:15:5: warning: operation on ‘a’ may be undefined". If your compiler has an option to enable warnings like these, use it! (Under gcc it's -Wsequence-point
or -Wall
. Under clang, it's -Wunsequenced
or -Wall
.)
See John Bode's answer for the detailed language from the C Standard that makes this expression undefined. See also the canonical StackOverflow question on this topic, Why are these constructs (using ++) undefined behavior?