Simplified for humans, anyway. Computers can do it either way.
More specifically, the task at hand is to bind operators to operands,
so as to build a "parse tree" (see, e.g.,
<
http://en.wikipedia.org/wiki/Parsing>, which has a little graphic
of how to bind "1 + 2 * 3" in the usual manner). If one uses an
"operator precedence grammar", to simplify the idea for humans,
one must specify the "precedence" of the various operators, hence
the name "operator precedence grammar".
As Andrey Tarasevich noted, the C standards (C89 and C99 both) use
a different method, so that Standard C does not need to talk about
precedence and associativity at all. (It still uses those words
anyway, since the grammar in the standard has an implied equivalent
operator-precedence grammar. In fact, it actually has multiple
implied equivalents, as we shall see.)
Well, sort of:
Associativity (or something very like it) is applicable in expressions
with unary operators if pre- and postfix operators are mixed.
Only if they are given the same "precedence", though.
The problem with a simple precedence grammar is that it does not
tell us what to do when all the operators have the *same* precedence.
In particular, given something like:
x op y op z
does this mean:
op
/ \
(x op y) op z op z
/ \
x y
or does it mean:
op
/ \
x op (y op z) x op
/ \
y z
? It could be either one -- so to "break the tie", we add a
complication to the already-complicated[%] "operator precedence"
grammar, where "precedence" tells us which operator(s) to bind
first[*]. We add "associativity" too; it tells us which operator(s)
to bind first in case "precedence" failed to tell us. It *only*
applies *after* precedence fails, though!
-----
% Yes, complicated: just less-so than the *really* complicated
fully-factored grammar in the C standards.
* Note that this is all about binding operators at *compile*
time. Actual evaluation order at runtime is another matter
entirely.
-----
*p++ could be ((*p)++) or (*(p++))
If we give "++" higher precedence than unary "*", then it can only
be the latter; we need not look for any associativity.
If the standard's grammar is mechanically transformed into an
operator-precedence grammar, the resulting grammar does in fact
give (postfix) "++" higher precedence than unary "*". (It gives
prefix "++" the same precedence, but operators bind to operands,
not to additional operators, so interpreting *++p as (*++)p is
right out.)
In their operator table K&R list * and ++ as right associative so the
above is interpreted as we all know it is.
Hence, K&R use a *different* grammar -- but one that achieves the
same result.
It is OK to change the grammar if the result is the same. Compiler
writers do this all the time, for various reasons. The C standards
require only that one get the correct answer; the method by which
this "right answer" is obtained is infinitely changeable.