0

What is the difference between ++*p++ and *++p++ (where p is a pointer) in C?

I keep getting an error when I do the second one; can somebody explain, as it's been in my head for days. I checked on Quora and other websites but I couldn't find anything useful.

I want to know why the first one is acceptable but not the latter.

#include <stdio.h>
int main()
{
    int arr[]={3,9,0,4,5};
    int *ptr=arr;
    printf("%d ",*++ptr++);
    printf("%d ",*ptr);
    return 0;
}
3
  • 1
    "Doctor, Doctor, it hurts when I do this."
    – BoP
    Commented Jul 9, 2022 at 7:45
  • Even if it works as intended it should be rejected in a code review.
    – Cheatah
    Commented Jul 9, 2022 at 7:46
  • Yup - there is no difference: both are vile. Commented Jul 9, 2022 at 9:58

2 Answers 2

6

The issue here is a question of operator precedence and the nature of the result of the postfix increment operator (i.e. p++).

In C, that postfix increment operator has the highest priority of all; then, the prefix increment and indirection (*) operator have equal priority, and have right-to-left associativity.

So, adding parentheses to your expressions, to clarify the order of evaluation, we get the following:

  1. *++ptr++ becomes *( ++(p++) )
  2. ++*ptr++ becomes ++( *(p++) )

Now remember that the result of the postfix operation is a so-called "rvalue" § – that is, something that can be used on the right-hand side of an assignment but not on the left-hand side. (For example, the constant, 3 is an rvalue: x = 3 is a valid operation but 3 = x is clearly not.)

We can now see that, in your first expression, inside the outer brackets that I have added, we are trying to increment the result of the p++ operation – and that is not allowed. However, in the second case, we are only dereferencing that result (which is a pointer) and then (outside the outer brackets) incrementing the pointed-to variable – which is allowed.

When I compile your code with clang-cl, the error is:

error : expression is not assignable

As (hopefully) explained above, the "expression" referred to is p++.


§ Formally, the result of the postfix increment operator is (a copy of) the value of its operand; that value is not modifiable or assignable.

1
  • You're right, C returns an rvalue for the prefix increment, and (modern) C++ doesn't have sequence points. I removed my comment
    – Elzaidir
    Commented Jul 9, 2022 at 16:54
2

This is definitely a strange and surprising result. To understand what's going on, it will help to take a closer result at what the "autoincrement" operators ++x and x++ really do.

Most expressions simply compute new values. If I say

a = b * 3;

that means, "take the value of the variable b, multiply it by 3, and that's the value we'll assign to a". Similarly, if I say

a = a + 1;

that means, "take the (old) value of the variable a, add 1 to it, and that's the new value we'll assign back to a".

But ++ is special, because it has the "assign the value back" part built in. Any time you use ++ (or --), two things are happening: we're computing a new value, but we're also modifying the variable whose value we just fetched.

To make this very clear, if I say

a = ++b;

that means, "take the value of the variable b, add 1 to it, assign that new value back to b, and that's the value we'll assign to a". That's for the "prefix" form ++b. For b++, it's a little different:

a = b++;

That means, "take the value of the variable b, add 1 to it, assign that new value back to b, but the value we'll assign to a is the old value of b, before we added 1 to it." In other words, the value of the subexpression b++, the value that "pops out" to participate in the larger expression, is the old value of b.

The other thing to keep in mind here is that when it comes to assigning values, we obviously need a variable to assign the value to. We can't say

3 = b * 3;               /* WRONG */

On the right-hand side of the = sign, we fetch b's value and multiply it by 3, but then where we store the new value? On the left-hand side of the = sign, 3 is not the name of a variable, nor is it any kind of a location where we can store a value. So an assignment like this is illegal.

(Formally, what we've been talking about here is the difference between an rvalue and an lvalue. Those are interesting and useful terms that you might want to learn about some day, perhaps even today, but I'm not going to say anything more about them for now.)

But now we have almost enough information to answer your original question. Let's look at the expression that worked:

++*ptr++

What the heck does that mean?

In one sense, it's kind of meaningless, because it's not something that you would probably ever write in a real program. It has very little practical value, which is actually kind of good, which means it's not so bad that, at first glance, it's pretty badly cryptic, in that it's not obvious what it should do.

To understand what it does, we have to be clear about the precedence. Which operands bind more tightly to their operands? Precedence is what tells us that if we write

1 + 2 * 3

the multiplication operator * binds more tightly, meaning that the expression is evaluated as if we had written

1 + (2 * 3)

Now, it happens that the autoincrement operator ++ binds more tightly that the unary contents-of operator *. That is, when we write

++*ptr++

the expression is evaluated as if we had written

++ *(ptr++)

So the first thing we're going to do, inside the parentheses, is ptr++. This means, as we saw before, "take the value of the variable ptr, add 1 to it, assign that new value back to ptr, but the value that pops out to the larger expression is the old value of ptr, before we added 1 to it."

And then the next thing that happens in the "larger expression" is the * or contents-of operator. * works on a pointer, and accesses the object pointed to by the pointer. In your original program, the object pointed to by the pointer ptr was the first cell of the array arr, that is, arr[1]. So * is going to operate on whatever the old value of ptr was, whatever ptr used to point to. And that's arr[0]. So what we end up doing is the equivalent of

++(arr[0])

We're going to take arr[0]'s old value, add 1 to it, store it back in arr[0], and (since this is prefix ++ we're talking about), the value that will "pop out" to the larger expression would be the new value of arr[0]. So, if you had written

printf("%d\n", ++*ptr++);

it would have printed the new value of arr[0], or 4.

The bottom line is that although ++*ptr++ is a complicated-looking expression that's hard to understand and does something so obscure that it might not even be useful, it does do something, and is legal.

So now, finally, it's time to look at

*++ptr++

What does that do?

The first thing we have to know is whether prefix ++ or postfix ++ binds more tightly. It's a question that hardly ever comes up (and we're about to see why), but the answer is that postfix ++ binds more tightly. So this expression is interpreted as if you had written

* ++(ptr++)

So, once again, the first thing we're going to do is take ptr's value, add 1 to it, store that new value back into ptr, and then the value that's going to "pop out" to the larger expression is going to be the old value — but only the old value — of ptr.

Let me say that again. The value that "pops out" to the larger expression is just the old value of ptr. By that time we no longer know or care that it was the variable ptr that we got this value from.

So then we come to the prefix ++. And now we have a serious problem. Remember, ++ wants to fetch a value from an object, add 1 to it, and store the new value back into an object. But at this point we don't have an object to fetch from or store to, we just have a value — remember, the old value of the variable ptr.

This will be easier to understand if we think about integer variables, instead of pointer-to-integer. Suppose I said

int a;
int b = 5;
a = ++(b++);

So we fetch b's value, which is 5, and add 1 to it, and store the new value — which is 6 — back in b, and the value that "pops out" to the larger expression s the old value of b. So now it's as if we had written

a = ++5;             /* WRONG */

And this makes no sense. We can't "fetch the old value from the variable 5", because 5 isn't a variable. It's just as wrong as when we said 3 = b * 3;, and for the same reason.

You might also be interested in Question 4.3 in the C FAQ list.

1
  • You say, Now, it happens that the autoincrement operator ++ binds more tightly that the unary contents-of operator ... but that's really only true for the postfix form; as I said in my answer, the prefix ++ and dereference operators have the same precedence. Commented Jul 9, 2022 at 13:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.