This would be my very first technical post. I would like to give special thanks to my buddy, Joshua Na, for exploring this topic with me in details over Skype on a Friday afternoon! It seems that I didn’t get this right all along throughout my ten years of programming, not until now! I would also like to thank Sergey for pointing out a possible fundamental error in the first cut of my article. For now, I supposed this article is considered complete. Please feel free to discuss in the comments below.
The Empirical Approach
This article started off as an empirical approach, where I try out different codes and attempt to explain the output by spotting the pattern, which explains the title of this article. I’m not sure whether I got this right as I didn’t manage to look at the assembly.
Going beyond, there were many comments that flowed in regarding this and I read up a whole lot more, looking at many conflicting school of thoughts on this. I decided I should just try to understand it my own way based on whatever I have. Does it really matter to know the absolute truth? Or perhaps it’s better to know how to apply. 😛
What are pre-increments and post-increments operators?
In case you are not sure what those stuff are, in codes, they are the ++:
int x = 1; //Pre increment - ++x int y1 = ++x; //Post increment - x++ int y2 = x++;
In a pre-increment (line 4), x will be incremented by one before y1 takes the value of (the new) x.
In a post-increment (line 7), y2 is going to take the value of x, after that, x gets incremented by one.
This looks very straight forward. It seems that in pre-increment, the ++ operator is evaluated first before the statement does while in post-increment, the statement gets evaluated first, before the post-increment operator.
The problem comes, when you try to refer to the Order of Precedence Table like this one:
Does Order of Precedence have anything to do with Order of Evaluation?
In many of the lectures that I sit through in the university, I remembered there seems to be such a table on order of precedence of operators shown to us when we talked about order of evaluation. Hence, if we were to follow the charts like the one above, and this is pretty much the same for Java, C++, C#, the post-increment is ranked higher than the pre-increment. This means that the post-increment is supposed to be evaluated first. To me, this doesn’t make any sense. In fact, I thought that the post-increment should be ranked all the way at the bottom as the increment only takes place after the statement is completed!
If you look through Stack Overflow and programming forums, you might find that quite a number of them will tell you that operator precedence has nothing to do with the order of evaluation. Well, that could be a reason for this strange phenomenon then. But that would have meant that those professors taught us the wrong stuff! What’s going on here?
Anyway, does the order of precedence have anything to do with the order of evaluation?
I would like to refer to some materials from two articles:
Order of precedence for operators defines how expressions are built from their source code representations.
Quoting from the example found in cppreference.com, the operator precedence will be able to tell us that f1() + f2() + f3() will be parsed as (f1() + f2()) + f3() due to the left-right associativity of the + operator. Note, this still does not explain which of the functions, f1, or f2 or f3, will get evaluated first.
The above example, or at least that’s how I like to interpret, says that operator precedence has no relationship with the order of evaluation.
The precedence and associativity of C operators affect the grouping and evaluation of operands in expressions. An operator’s precedence is meaningful only if other operators with higher or lower precedence are present. Expressions with higher-precedence operators are evaluated first.
From Precedence and Order of Evaluation, MSDN
The article from MSDN seems to suggest otherwise.
MSDN states that operator precedence is meaningful only if other operators with higher or lower precedence are present. This is perfectly understandable or otherwise, there will be nothing to compare with. Hence, for my experiments, I decided to put pre-increment and post-increment operators in an expression where the variable is used again in a single expression, something like this one:
i = ++i + i++;
I understand that from cppreference.com, said statements like the one above are considered undefined behaviours.
Nevertheless, I did try it, and the experiments produced deterministic results. In the next few paragraphs below, I would like to share my findings with you:
Experiment Group 1
Consider the following codes:
int x = 1; int y = x++ + x;
The value of y is 3. The post-increment does not happen at the end of the statement (or at least that was how I thought I was taught in uni). What is happening here is this:
- x++ gets evaluated.
- The x in x++ (ie. the first x) retains the value of 1.
- The increment will take effect for the second x, making the value of the second x, 2.
- Hence, y = 1 + 2 = 3
The example above shows that x++ does not wait till the evaluation of the statement before the increment takes place. In fact, it probably got evaluated first, which is in coherence to the operator precedence table. All the post-increment operator was saying is that the original x attached to the post-increment operator will not be incremented.
I experimented with expressions that contain both x++ and ++x, and it seems that x++ will get computed first before ++x as per the chart.
Experiment Group 2
Consider the following codes:
int x = 1; int y = x + x++;
Now that we know that the post-increment operator will get evaluated first, I expected that the value of y would be 3, going by the very same steps I listed above.
Actually, the answer is no! The value of this y is 2.
Let’s consider another set of codes:
int x = 1; int y = x + x++ + x;
In the above codes, the value of y is 4. How does that happen? This is my theory:
- x++ gets evaluated.
- The x in x++ (ie. the second x) retains the value of 1.
- The increment will take effect for all x that exist to the right of x++, making the value of the third x, 2. The first x however, because it is on the left side of the equation, will not be incremented.
- Hence, y = 1 + 1 + 2 = 4
I tried out with many more examples, using all four pre and post increments and decrements operators. The results seems consistent with my thinking.
As to exactly why this is so, I’m not sure. It might have been something to do with the memory stack or the way codes are compiled that it seems impossible to apply the increments towards the left. Anyway, this is definitely cleaner if there are a lot of increments and decrements that are found within the same statement.
Results of the Experimentation
I think I will refuse to give an actual answer on whether operator precedence equates to evaluation precedence. I probably can’t answer that based on the conflict material that the Internet had to present.
BIG DISCLAIMER FIRST: The next statement would be a personal opinion on how I understand and how I would like to see this whole situation as. I would like to interpret these findings as a yes, the operator precedence does affect evaluation precedence. Even if not directly, then indirectly somehow.
And based on the results of my experimentations, it also appears that pre-increments and post-increments have very deterministic rules as well, at least on the two systems that I am using:
- Post-increments will be evaluated first. The variable with the post-increment operator will retain its value. The same variable found to the right in the very same expression will be incremented as well.
- Pre-increments will be evaluated next. The variable with the pre-increment operator will be incremented. The same variable found to the right in the very same expression will be incremented as well.
- When multiple post-increments or pre-increments are found in the same equation, evaluation will take place from the left to the right.
- And yes, there is probably nothing wrong with the Order of Precedence table. It just is what it is.
I hope you find this article useful. If you have any feedback or any other views on how it should work, please leave me a comment. I would love to hear more from you. 🙂
However, especially for those who are more confused after reading all these, perhaps a better solution is for us to stop writing complicated codes. I’m sure we can achieve the same computation across several statements rather than mixing variables undergoing increments with the same variables in a single expression. Pretty sure it will not make any difference to the performance of the application. Let’s keep it simple shall we?