trap? ++x // y versus x++ // y
Tracy Allen
Posts: 6,664
It's a Spin peculiarity I've run afoul of more than once. You might expect the two forms
x := x++ // 10
and
x := ++x // 10
to give the same result. That is, a sequence that cycles from zero to 9 and repeats. However, only the second form works as expected. The first form with x++ simply sticks at the starting value of x.
Is there a logic behind that difference?
x := x++ // 10
and
x := ++x // 10
to give the same result. That is, a sequence that cycles from zero to 9 and repeats. However, only the second form works as expected. The first form with x++ simply sticks at the starting value of x.
Is there a logic behind that difference?
[size=1]CON _clkmode = xtal1 + pll8x ' _xinfreq = 5_000_000 OBJ fds : "fullDuplexSerial" PUB PlusplusSlashslashTester | x1,x2 fds.Start(31,30,0,9600) waitcnt(clkfreq/10+cnt) fds.str(string(13,"top",13)) repeat waitcnt(clkfreq/10+cnt) x1 := x1++ // 10 x2 := ++x2 // 10 fds.tx(13) fds.dec(x1) fds.tx(32) fds.dec(x2) [/size]
Comments
-Phil
++x works because the increment is applied to the result used for following work, including the div mod and the assignment.
As expected, it outputs 1.
But look at this program:
I would have expected the output to be 2, but it's 3! In other words, one of the post-increments executes before the assignment takes place -- unlike the case in the first example.
Here's another example:
As expected, the output is 2. But in this program,
the output is 3! (I guess addition is no longer commutative?)
It appears that the rule is this: post-increments are deferred only until the incremented variable is encountered again in the expression.
-Phil
True, but that doesn't address the issue of how long the increment(s) should be deferred (i.e. until the end of the expression or until the next time the variable is encountered in the expression).
-Phil
Does not look like it's deferred.
As soon it's pushed on to the stack the VAR location that holds the value is incremented.
If the that same VAR location is used again, it will have the new value already.
-Phil
A statement like:
y = a++ + b++ + c++
Is actually changing 4 variables with a single assignment. Crazy nuts! There are no "logical underpinnings" to this, only perhaps some arbitrary rules of evaluation decided by the language designer.
Confusion over which order what happens has led to many a bug. Or a least wasted a lot of time with confusion. As this thread nicely demonstrates:)
These pre and post increment operators are a hangover from the days when compilers could not optimize as much as they do now. A way for the programmer to tell the compiler to use INC and DEC instructions rather than generating a sequence of code to fetch a small number and add it.
Generally modern compilers can optimize away the less confusing but slightly more verbose way of expressing what you want to do. For example the following two loops both take 10 seconds +/- a random millisecond or two depending how the OS feels when it runs it: How such code get compiled in Spin and what the comparative run times there are I have no idea.
Pre-incrementing/decrementing should still retain the LHS's L-value so the assignment can take place. The Spin compiler doesn't like it, though.
-Phil
Don't they know?
Surely the C language specification spells out undefined behaviour, things that are implementation dependent.
Or does GCC actually specify the behaviour, like it's many other extensions to C, but it's just warning that other compilers may do things differently.
I don't think that the amount of optimization should have any bearing on this. Either the behaviour is defined and the optimizer makes no difference to the outcome. Or the behaviour is undefined in which case it may do.
One of my favorite things about the Propeller is using it to time itself.
Fastest (time = 1)
Slower (time = ~1.2)
Slowest (time = ~1.5)
Interesting results. As we expected I guess except verbose versions are not as bad as I might have guessed.
I was not being lazy there. As it happens I have totally run out of Props and PropPlugs here.
I did try C versions of my examples. First attempt resulted in the compiler optimizing the entire loop away and compiling down to a printf of the final result !
Modern optimizers are wonderful things.
I'm thinking of it now in terms of the result variable, in relation to the variable X that is under consideration at each step in evaluation of the expression. Normal operators act on the result only, but the assignment operators, they always act on the result, and they act back on the variable itself either before or after doing their bit with the result. I understand that happens internally using some not-so-fancy footwork with the expression stack.
The pathological cases make sense in that context.
I'm pretty careful with making tricky clever expressions using the assignment operators. Or, too, using subsidiary assignments within expressions. There is always that feeling of potential misunderstanding. Always needs to be verified. But as Jon pointed out, it can be significantly faster than the straight step by step.
I've run into this trap too, in this example thinking to do something at 10 seconds and again at 60 seconds. That of course works okay with ++ tocks in the first statement, or the tocks++ assignment transferred to the last statement.
-Tor