[comp.lang.c] Increment Operators vs. Precedence

thomson@zazen.macc.wisc.edu (Don Thomson) (03/06/91)

So here I am teaching an introductory C class and have just finished
precedence and associativity and moved on to prefix and postfix increment
operators.  The example on the board is y = x++, and I explain that x gets
assigned to y before x is incremented.  An astute individual in the back of
the room raises his hand and points out that according to what I just taught
them, the precedence is wrong, that in fact the assignment operator has a
significantly lower precedence than ++.  So the dilemma is how to explain that
precedence is not the issue here, that the order of operations is tied to the
definition of prefix versus postfix increment operators.  Any ideas on how to
word the explanation in such a way that I don't leave them forever suspicious
about what does and what does not have to obey precedence rules?
--
 
----- Don Thomson ----- MACC, 1210 W. Dayton, Madison, WI  53706 -------------
    (608) 262-0138      thomson@macc.wisc.edu / thomson@wiscmacc.bitnet

torek@elf.ee.lbl.gov (Chris Torek) (03/06/91)

In article <THOMSON.91Mar5173421@zazen.macc.wisc.edu>
thomson@zazen.macc.wisc.edu (Don Thomson) writes:
>The example on the board is y = x++ ...  So the dilemma is how to explain
>that precedence is not the issue here, that the order of operations is
>tied to the definition of prefix versus postfix increment operators.

Precedence is purely syntactic; order of operations is semantic.

All `precedence' does is assign some particular grouping to a set of
symbols that otherwise have none.  For instance, given

	x = a + b * c++;

there is no grouping, and precedence (and associativity) is a simple way
of describing how the expression is parsed.

Exclusive of precedence and associativity, all of the following are possible
parses for the sentence above:

	((x = a) + (b * c))++

	(x = (a + b)) * (c++)

	(((x = a) + b) * c)++

	x = (a + (b * (c++)))

The last one is the one we actually want, and we can obtain it by using
precedence and associativity rules:

	post-increment > multiply > add > assign

In

	a = b = 0;

a right-associative rule for assignment is necessary to force

	a = (b = 0)

rather than

	(a = b) = 0

, and in

	x = a / b * c / d;

a left-associative rule is necessary to force

	x = (((a / b) * c) / d)

rather than

	x = ((a / (b * c)) / d)

or some such.

All of these simply give us some particular parse tree for some sentence.
It is up to us to assign semantics to each parse.  The semantics for `c++'
are `get the current value of c, and eventually---by the time you reach
the next sequence point---store that value, plus 1, in c'.

So in

	y = x++;

the parse tree looks like this:


		=

	     /     \
	    /       \
	   /         \

	  y	 postfix-++

		     |

		     x

or in list notation, (= y (postfix-++ x)), but the order of operations is:

	A. fetch current value of x

	B. [store in y] and [add 1 to x]

In particular, in an expression like:

	x = ++x;

we have a very specific parse tree, namely (= x (pre++ x)), but we may have
a nonspecific result, depending on implementation details:

	A. fetch current value of x
	B. [add 1 and store in x] and [add 1 to x]

If preincrement is implemented internally as:

	- put an `add 1 to x' on the list of things to do
	- fetch value of x and add 1
	- store in x
	- do the list of things to do

this expression might add 2 to x.  If, on the other hand, it is
implemented as:

	- fetch current value of x, add 1, store result in x, use result
	- store result in x

the expression will add 1 to x.  Since a compiler could even
(conceivably) `interleave' operations, it is possible that this might
act like `x += 0x101'.%  This is rather unlikely, but explains why no
more than one side effect should be applied to any individual object
between sequence points.
----
% Consider a machine in which `add 1 to x' is implemented as `fetch low byte
 of x, add 1, store; if result is 0, fetch high byte of x, add 1, store'
 and where there are two `processing units' and some peculiar delays:

		unit 1:				unit 2:
		------				------
	fetch low byte of x: (255)	(busy with something else)
	add 1: (0)			fetch low byte of x: (255)
	store: (low byte of x now 0)	add 1: (0)
	(delay)				store: (low byte of x still 0)
	(delay)				fetch high byte of x: (17)
	(delay)				add 1: (18)
	(delay)				store: (x is now 1 larger)
	fetch high byte of x: (18)	(do something else)
	add 1: (19)
	store: (x is now 0x101 larger)
-- 
In-Real-Life: Chris Torek, Lawrence Berkeley Lab EE div (+1 415 486 5427)
Berkeley, CA		Domain:	torek@ee.lbl.gov

xor@aix01.aix.rpi.edu (Joseph Schwartz) (03/06/91)

In article <THOMSON.91Mar5173421@zazen.macc.wisc.edu> thomson@zazen.macc.wisc.edu (Don Thomson) writes:
>So here I am teaching an introductory C class and have just finished
>precedence and associativity and moved on to prefix and postfix increment
>operators.  The example on the board is y = x++, and I explain that x gets
>assigned to y before x is incremented.  An astute individual in the back of
>the room raises his hand and points out that according to what I just taught
>them, the precedence is wrong, that in fact the assignment operator has a
>significantly lower precedence than ++.  So the dilemma is how to explain that
>precedence is not the issue here, that the order of operations is tied to the
>definition of prefix versus postfix increment operators.  Any ideas on how to
>word the explanation in such a way that I don't leave them forever suspicious
>about what does and what does not have to obey precedence rules?

I'd explain that every expression has a value, and that some operators have
side effects as well.  In your example, you confuse the issue by saying that
the assignment occurs before the increment.  That isn't strictly true.
In fact, the expression x++ has a VALUE of x, and it has the side-effect
of incrementing x after the value is determined.  The = operator also has
a side-effect: it assigns a value to y.  So here's how I'd break down your
example:
 
y = x++  is equivalent to  y = (x++)  because of precedence rules.
 
(x++)  is an expression whose value is the current value of x.  It has
the side-effect of incrementing x AFTER its value is determined.
 
y = foo  is an expression whose value is the value of y.  It has the
side-effect of assigning the value of foo to y, BEFORE the value of y
is determined.

The tricky part is explaining whether side-effects occur before or after
the value of an expression is determined.  In expressions like a = b,
a += b, a *= b, a/= b, a -= b, a %= b, a >>= b, a <<= b, ++a, and --a,
the side-effect happens before the value of a is determined.  Only in
expressions like a++ and a-- does the side-effect happen afterwards.
(Am I forgetting any other operators with side-effects?)
 
I don't think of precedence rules playing a very large part in this
explanation.  Precedence rules determine what the individual sub-
expressions are; they don't determine the order that side-effects
occur in.

horne-scott@cs.yale.edu (Scott Horne) (03/07/91)

In article <THOMSON.91Mar5173421@zazen.macc.wisc.edu> thomson@zazen.macc.wisc.edu (Don Thomson) writes:
<
<The example on the board is y = x++, and I explain that x gets
<assigned to y before x is incremented.  An astute individual in the back of
<the room raises his hand and points out that according to what I just taught
<them, the precedence is wrong, that in fact the assignment operator has a
<significantly lower precedence than ++.  So the dilemma is how to explain that
<precedence is not the issue here, that the order of operations is tied to the
<definition of prefix versus postfix increment operators.  Any ideas on how to
<word the explanation in such a way that I don't leave them forever suspicious
<about what does and what does not have to obey precedence rules?

Suppose you have the following:

	int     x = 37,
	        y;

	y = x++;

`x++' is an expression with the value 37.  It has the side effect of setting
the value of `x' to 38.  This evaluation does occur before the assignment of
37 to `y', but it doesn't affect `y'.

`++x' has the value 38.  It happens to have the side effect of setting `x'
to the same value.

					--Scott

-- 
Scott Horne                               ...!{harvard,cmcl2,decvax}!yale!horne
horne@cs.Yale.edu      SnailMail:  Box 7196 Yale Station, New Haven, CT   06520
203 436-1817                    Residence:  Rm 1817 Silliman College, Yale Univ
Uneasy lies the head that wears the _gao1 mao4zi_.

fred@mindcraft.com (Fred Zlotnick) (03/07/91)

In article <THOMSON.91Mar5173421@zazen.macc.wisc.edu> thomson@zazen.macc.wisc.edu (Don Thomson) writes:
>So here I am teaching an introductory C class and have just finished
>precedence and associativity and moved on to prefix and postfix increment
>operators.  The example on the board is y = x++, and I explain that x gets
>assigned to y before x is incremented.  An astute individual in the back of
>the room raises his hand and points out that according to what I just taught
>them, the precedence is wrong, that in fact the assignment operator has a
>significantly lower precedence than ++.  So the dilemma is how to explain that
>precedence is not the issue here, that the order of operations is tied to the
>definition of prefix versus postfix increment operators.  Any ideas on how to
>word the explanation in such a way that I don't leave them forever suspicious
>about what does and what does not have to obey precedence rules?

A friend of mine who teaches at Mills College just asked me a remarkably
similar question.  The best explanation I could come up with is that
precedence does not specify order of evaluation.  It describes the default
parenthesization (if there is such a word).  Thus the precedence rule you
cited just means that "y = x++" is interpreted as "y = (x++)".  The value
of the expression (x++) is the value of x before being incremented.  (If the
precedence was reversed, it would be interpreted as "(y = x)++", which is
meaningless.)  A related example is the conditional expression
	x++ && y++ || z++
The precedence rules state that this means
	((x++) && (y++)) || (z++)
As it happens, there is an order of evaluation rule for && and ||, and it
causes strange results here: x is always incremented, y is incremented
if x was not -1, and z is incremented only if x and y were both -1.  Thus
even though ++ is the highest precedence operator in this expression, not
all ++'s get evaluated.  Note that for most C operators (anything except &&,
||, ?: and comma) there is no specified order of evaluation.

Hope this helps.
Fred Zlotnick                       |	#include <std.disclaimer>
fred@mindcraft.com                  |	#include <brilliant.quote>
...!{decwrl,ames,hpda}!mindcrf!fred |

torek@elf.ee.lbl.gov (Chris Torek) (03/07/91)

In article <10648@dog.ee.lbl.gov> I left out one word:
-All `precedence' does is assign some particular grouping to a set of
-symbols that otherwise have none.  For instance, given
-
-	x = a + b * c++;
-
-there is no
	   explicit
-	      grouping, and precedence (and associativity) is a simple way
-of describing how the expression is parsed.

(It makes much more sense this way.)
-- 
In-Real-Life: Chris Torek, Lawrence Berkeley Lab EE div (+1 415 486 5427)
Berkeley, CA		Domain:	torek@ee.lbl.gov

willcr@bud.sos.ivy.isc.com (Will Crowder) (03/08/91)

In article <668294528.3716@mindcraft.com>, fred@mindcraft.com (Fred Zlotnick)
writes:

|> A related example is the conditional expression
|> 	x++ && y++ || z++
|> The precedence rules state that this means
|> 	((x++) && (y++)) || (z++)
|> As it happens, there is an order of evaluation rule for && and ||, and it
|> causes strange results here: x is always incremented, y is incremented
|> if x was not -1, and z is incremented only if x and y were both -1.  Thus
|> even though ++ is the highest precedence operator in this expression, not
|> all ++'s get evaluated.  Note that for most C operators (anything except &&,
|> ||, ?: and comma) there is no specified order of evaluation.

Actually, x is always incremented, y is incremented if x wasn't 0 (not -1)
and z is incremented if either x or y is 0.

Remember, the value of the expression (x++) is x *before* the increment, not
after.  Since all increment operators in this expression are postincrement,
the incremented values of x, y and z do not come into play in determining
which parts of the expression are evaluated.

Due to C's (rather handy) shortcircuiting of && and || operators, the right
side of the || is only evaluated if the left side is false.  For &&, the
right side is only evaluated if the left side is true.  Since the left side
of the || expression in this case is the && expression, the && expression
must be false (either x or y 0) for the right side to be evaluated.

Hope this helps,

Will

|> Hope this helps.
|> Fred Zlotnick                       |	#include <std.disclaimer>
|> fred@mindcraft.com                  |	#include <brilliant.quote>
|> ...!{decwrl,ames,hpda}!mindcrf!fred |

peter@ficc.ferranti.com (Peter da Silva) (03/08/91)

In article <THOMSON.91Mar5173421@zazen.macc.wisc.edu> thomson@zazen.macc.wisc.edu (Don Thomson) writes:
> The example on the board is y = x++, and I explain that x gets
> assigned to y before x is incremented.

So you used loose terminology. And confused them. What you meant was that
the value of x as it was before incrementing is assigned to y. X may in
fact have been assigned first, as in:

	y[x] = x++

which has an undefined result.

> So the dilemma is how to explain that
> precedence is not the issue here, that the order of operations is tied to the
> definition of prefix versus postfix increment operators.

Don't explain that, because it's not in fact true. If you leave them
believing that they will believe the expression I gave above has a defined
result. Explain it in terms of expressions, their values, and side effects:

	"x++" is an expression that returns the value of x as it was
	before incrementing, and as a side effect increments x.

Precedence is not the issue, but neither is the order of operations.
-- 
Peter da Silva.  `-_-'  peter@ferranti.com
+1 713 274 5180.  'U`  "Have you hugged your wolf today?"