What is the value of I after the following:
- Code: Select all
INT J = 9;
INT I = 1;
INT I = (((++J) + (J++)) + J++) + I++;
Answer:
Undefined.
Everyone should know what the ++ and -- operators are, and everyone should know that the “prefix increment” (++I) works before the statement is executed while the “postfix” increment (I++) works after, but there are a lot of other rules no one ever cares to mention.
For most people, this extra information isn’t important to know. If you code like the above then you deserve to have your programs backfire.
Although you should always avoid ambiguous (and undefined) coding constructs such as the above, you should still understand how they work and why they are so bad.
The above code is undefined. Why?
According to the C99 specifications (the document that lets everyone know the rules for C—every compiler is supposed to conform to these rules): “Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored.”.
This rule raises 2 questions:
1: Why?
2: What does it mean by “sequence points”?
#1: Because the document offers no specifications for how the compiler is supposed to implement the -- and ++ operators. It leaves it up to the compiler to decide how to make the -- and ++ operators work.
#2: A sequence point defines any point in a computer program’s execution at which it is guaranteed that all side effects of previous evaluations will have been performed, and no side effects from subsequent evaluations have yet been performed (from Wikipedia).
In other words, inside the code statement above, we are guaranteed that the ++J has already been done, but none of the J++’s or the I++.
After the statement finishes, the sequence point must be “closed” (for lack of better explaining) by finishing all the remaining ++ operations.
The sequence points are in the following places:
* The semicolon (;).
* The non-overloaded comma-operator (,).
* The non-overloaded || operator.
* The non-overloaded && operator.
* The ternary ?: operator.
* After evaluation of all a function's parameters but before the first expression within the function is executed.
* After a function’s returned object has been copied back to the caller, but before the code just after the call has yet been evaluated.
* After the initialization of each base and member.
Microsoft additionally uses the following (which are a bit redundant):
* The controlling expression in a selection (if or switch) statement.
* The controlling expression of a while or do statement.
* Each of the three expressions of a for statement.
When you enter any of these situations in your code, your prefix -- and ++ will all be executed, then your coded expression will be evaluated, and then all of the postfix -- and ++ operations will be executed.
Knowing this, you might think that you should be able to define an actual answer to the code in the example above.
But you can’t, because as mentioned each compiler may implement ++ and -- in its own way.
If you have 2 of these on the same variable within a sequence point, the compiler may decide to do something along the following logic:
1: Buffer 1st into a register.
2: Buffer 2nd into register (which is the same as 1st because it is the same variable).
3: Increase buffer 1 and write the value to 1st. 1st has been increased.
4: Increase buffer 2 and write the value to 2nd. But 2nd is actually the same variable as 1st, so we just overwrote everything we did in buffer 1.
The result would be that the variable would be incremented only once.
This is just an example.
If you did not already, now you know why this is terrible coding practice and should always be avoided. Other good reasons to avoid this include the simple fact that it is hard to read, nevermind the fact that it will not work the same from one compiler to the next.
My implementation of -- and ++ in L. Spiro Script uses a purely logical approach and would give you the value you would expect if you interpreted the rules strictly, however you should still never use it in your code.
L. Spiro