1
votes

I'm confused about the precedence and associativity of postfix/prefix operators.

On one hand, as I'm reading K&R book, it states that:

(*ip)++

The parentheses are necessary in this last example; without them, the expression would increment ip instead of what it points to, because unary operators like * and ++ associate right to left.

No mention whatsoever of a difference of associativity between postfix/prefix operators. Both are treated equally. The book also states that * and ++ have the same precedence.

On the other hand, this page states that:

1) Precedence of prefix ++ and * is same. Associativity of both is right to left.

2) Precedence of postfix ++ is higher than both * and prefix ++. Associativity of postfix ++ is left to right.

Which one should I trust? Is it something that changed with the C revisions over the years?

3
Find (at least) a third source?Scott Hunter
Personally, I trust this one. Of course, you can always not trust any of them and just hit up the language standard itself.WhozCraig
@ScottHunter since K&R book is supposed to be a bible, it remains confusing.fassn
The K&R book is a good starting point, but the ANSI/ISO standards are the true scripture.Christian Gibbons
Me, I trust this reference (although I'm biased).Steve Summit

3 Answers

4
votes

TL;DR: the two descriptions are saying the same thing, using the same words and symbols with slightly different meaning.

On one hand, as I'm reading K&R book, it states that:

(*ip)++

The parentheses are necessary in this last example; without them, the expression would increment ip instead of what it points to, because unary operators like * and ++ associate right to left.

No mention whatsoever of a difference of associativity between postfix/prefix operators. Both are treated equally. The book also states that * and ++ have the same precedence.

It's unclear which edition of K&R you're reading, but the first, at least, does treat the prefix and postfix versions of the increment and decrement operators as a single operator each, with effects depending on whether their operand precedes or follows them.

On the other hand, this page states that:

1) Precedence of prefix ++ and * is same. Associativity of both is right to left.

2) Precedence of postfix ++ is higher than both * and prefix ++. Associativity of postfix ++ is left to right.

The language standard and most modern treatments describe the prefix and postfix versions as different operators, disambiguated by their position relative to their operand. The rest of this answer explains how this is an alternative description of the same thing.

Observe that when only unary operators are involved, associativity questions arise only between one prefix and one postfix operator of the same precedence. Among a chain of only prefix or only postfix operations, there is no ambiguity with respect to how they associate. For example, given - - x, you cannot meaningfully group it as (- -) x. The only alternative is - (- x).

Next, observe that all the highest-precedence operators are postfix unary operators, and that in K&R, all the second-precedence operators are prefix unary operators except ambi-fix ++ and --. Applying right-to-left associativity to the second-precedence operators, then, disambiguates only expressions involving postfix ++ or -- and a prefix unary operator, and does so in favor of the postfix operator. This is equivalent to the modern approach of distinguishing the postfix and prefix versions of those operators and assigning higher precedence to the postfix versions.

To get the rest of the way to the modern description, consider the observations I already made that associativity questions arise for unary operators only when prefix and postfix operators are chained, and that all the highest-precedence operators are postfix unary operators. Having distinguished postfix ++ and -- as separate, higher-precedence operators than their prefix versions, one could put them in their own tier between the other postfix operators and all the prefix operators, but putting them instead in the same tier with all the other postfix operators changes nothing about how any expression is interpreted, and is simpler. That's how it is usually represented these days, including in your second resource.

As for left-to-right vs. right-to-left associativity, the question is, again, moot for a precedence tier containing only prefix or only postfix operators. However, describing postfix operators as associating left-to-right and prefix operators as associating right-to-left is consistent with their semantic order of operations.

2
votes

You can refer to the C11 standard although its section on precedence is a little hard to follow. See sec. 6.5.1. (footnote 85 says "The syntax specifies the precedence of operators in the evaluation of an expression, which is the same as the order of the major subclauses of this subclause, highest precedence first.")

Basically, postfix operators are higher precedence than prefix because they come earlier in that section, 6.5.2.4 vs. 6.5.3.1. So K&R is correct (no surprise there!) that *ip++ means *(ip++), which is different from (*ip)++, however its point about it being due to associativity is a bit misleading I'd say. And the geeksforgeeks site's point #2 is also correct.

1
votes

@GaryO's answer is spot on! Postfix has higher precedence because they come earlier.

Here's a small test to sanity check to convince yourself. I made two integer arrays and a pointer to the start of each array, then ran (*p)++ and *p++ on the two pointers. I printed out the pointer and array state before and after for reference.

#include <stdio.h>

#define PRINT_ARRS printf("a = {%d, %d, %d}\n", a[0], a[1], a[2]); \
printf("b = {%d, %d, %d}\n\n", b[0], b[1], b[2]); 

#define PRINT_PTRS printf("*p1 = a[%ld] = %d\n", p1 - a, *p1); \
printf("*p2 = b[%ld] = %d\n\n", p2 - b, *p2);

int main()
{
int a[3] = {1 , 1, 1}; 
int b[3] = {10,10, 10};

int *p1 = a;
int *p2 = b;


PRINT_ARRS
PRINT_PTRS

printf("(*p1)++: %d\n", (*p1)++);
printf("*p1++  : %d\n\n", *p2++);

PRINT_ARRS
PRINT_PTRS

}

Compiling with gcc and running on my machine produces:

a = {1, 1, 1}
b = {10, 10, 10}

*p1 = a[0] = 1
*p2 = b[0] = 10

(*p1)++: 1
*p2++  : 10

a = {2, 1, 1}
b = {10, 10, 10}

*p1 = a[0] = 2
*p2 = b[1] = 10

You can see that (*p1)++ increments the array value while *p2++ increments the pointer.