Post-increment on a dereferenced pointer?
Asked Answered
C

13

71

Trying to understand the behaviour of pointers in C, I was a little surprised by the following (example code below):

#include <stdio.h>

void add_one_v1(int *our_var_ptr)
{
    *our_var_ptr = *our_var_ptr +1;
}

void add_one_v2(int *our_var_ptr)
{
    *our_var_ptr++;
}

int main()
{
    int testvar;

    testvar = 63;
    add_one_v1(&(testvar));         /* Try first version of the function */
    printf("%d\n", testvar);        /* Prints out 64                     */
    printf("@ %p\n\n", &(testvar));

    testvar = 63;
    add_one_v2(&(testvar));         /* Try first version of the function */
    printf("%d\n", testvar);        /* Prints 63 ?                       */
    printf("@ %p\n", &(testvar));   /* Address remains identical         */
}

Output:

64
@ 0xbf84c6b0

63
@ 0xbf84c6b0

What exactly does the *our_var_ptr++ statement in the second function (add_one_v2) do since it's clearly not the same as *our_var_ptr = *our_var_ptr +1?

Cinemascope answered 13/5, 2009 at 19:1 Comment(1)
Possible duplicate of Pointer expressions: *ptr++, *++ptr and ++*ptrFranciscafranciscan
C
54

Due to operator precedence rules and the fact that ++ is a postfix operator, add_one_v2() does dereference the pointer, but the ++ is actually being applied to the pointer itself. However, remember that C always uses pass-by-value: add_one_v2() is incrementing its local copy of the pointer, which will have no effect whatsoever on the value stored at that address.

As a test, replace add_one_v2() with these bits of code and see how the output is affected:

void add_one_v2(int *our_var_ptr)
{
    (*our_var_ptr)++;  // Now stores 64
}

void add_one_v2(int *our_var_ptr)
{
    *(our_var_ptr++);  // Increments the pointer, but this is a local
                       // copy of the pointer, so it doesn't do anything.
}
Cawley answered 13/5, 2009 at 19:5 Comment(8)
That isn't true about the order of operation. In add_one_v2, the ++ is applied to the pointer, not the dereference. However, since it is a post increment, the dereference is happening BEFORE the increment.Toratorah
Are you talking about the original add_one_v2, or one of my examples with parentheses?Cawley
I'm talking about the original. I'm just trying to point out that your statement of "is incrementing the pointer and then dereferencing..." is incorrect.Toratorah
Thanks for the quick answer (and for the informative comment Torlack!). ++*our_var_ptr seems to 'work' the same as add_one_v1. One thing i don't understand though: if the functions are only working with local copies of the pointers --> how do they achieve to influence the value printed in main? Or do i misinterpret that statement?Cinemascope
The pointer itself is a local copy, but the value it is pointing at isn't.Toratorah
@Torlack: Gotcha, thanks for the reminder. Fixed. @ChristopheD: If you dereference a pointer, you can change the value stored in that location in memory. So, basically, you have a copy of the memory address of the value—you can't influence what memory address the pointer will hold in main, but you can change what value that memory address points to.Cawley
Thanks a lot (htw, torlack): that makes sense!Cinemascope
@Toratorah do you know how the compiler keeps track of that order of operations? If ++ has higher precedence then why does it not occur before the dereference? Is there a good term for this concept of order of operations outside of "operator precedence"?Eberly
C
103

This is one of those little gotcha's that make C and C++ so much fun. If you want to bend your brain, figure out this one:

while (*dst++ = *src++) ;

It's a string copy. The pointers keep getting incremented until a character with a value of zero is copied. Once you know why this trick works, you'll never forget how ++ works on pointers again.

P.S. You can always override the operator order with parentheses. The following will increment the value pointed at, rather than the pointer itself:

(*our_var_ptr)++;
Chaussure answered 13/5, 2009 at 19:9 Comment(1)
This example, along with quite a few other “Look ma, no hands!” code snippets, appears in that bastion of C knowledge, The C Programming Language.Vesicate
C
54

Due to operator precedence rules and the fact that ++ is a postfix operator, add_one_v2() does dereference the pointer, but the ++ is actually being applied to the pointer itself. However, remember that C always uses pass-by-value: add_one_v2() is incrementing its local copy of the pointer, which will have no effect whatsoever on the value stored at that address.

As a test, replace add_one_v2() with these bits of code and see how the output is affected:

void add_one_v2(int *our_var_ptr)
{
    (*our_var_ptr)++;  // Now stores 64
}

void add_one_v2(int *our_var_ptr)
{
    *(our_var_ptr++);  // Increments the pointer, but this is a local
                       // copy of the pointer, so it doesn't do anything.
}
Cawley answered 13/5, 2009 at 19:5 Comment(8)
That isn't true about the order of operation. In add_one_v2, the ++ is applied to the pointer, not the dereference. However, since it is a post increment, the dereference is happening BEFORE the increment.Toratorah
Are you talking about the original add_one_v2, or one of my examples with parentheses?Cawley
I'm talking about the original. I'm just trying to point out that your statement of "is incrementing the pointer and then dereferencing..." is incorrect.Toratorah
Thanks for the quick answer (and for the informative comment Torlack!). ++*our_var_ptr seems to 'work' the same as add_one_v1. One thing i don't understand though: if the functions are only working with local copies of the pointers --> how do they achieve to influence the value printed in main? Or do i misinterpret that statement?Cinemascope
The pointer itself is a local copy, but the value it is pointing at isn't.Toratorah
@Torlack: Gotcha, thanks for the reminder. Fixed. @ChristopheD: If you dereference a pointer, you can change the value stored in that location in memory. So, basically, you have a copy of the memory address of the value—you can't influence what memory address the pointer will hold in main, but you can change what value that memory address points to.Cawley
Thanks a lot (htw, torlack): that makes sense!Cinemascope
@Toratorah do you know how the compiler keeps track of that order of operations? If ++ has higher precedence then why does it not occur before the dereference? Is there a good term for this concept of order of operations outside of "operator precedence"?Eberly
M
54

OK,

*our_var_ptr++;

it works like this:

  1. The dereference happens first, giving you the memory location indicated by our_var_ptr (which contains 63).
  2. Then the expression is evaluated, the result of 63 is still 63.
  3. The result is thrown away (you aren't doing anything with it).
  4. our_var_ptr is then incremented AFTER the evaluation. It's changing where the pointer is pointing to, not what it's pointing at.

It is effectively the same as doing this:

*our_var_ptr;
our_var_ptr = our_var_ptr + 1; 

Make sense? Mark Ransom's answer has a good example of this, except he actually uses the result.

Mencius answered 13/5, 2009 at 19:50 Comment(4)
GCC doesn't accept: *our_var_ptr++; It accepts as: *(our_var_ptr)++Betrothal
Also IMO it should be *our_var_ptr; *our_var_ptr = *our_var_ptr + 1;Betrothal
@Betrothal I guarantee GCC will accept *our_var_ptr++. It might throw a warning, but if you took that away, you'd break SOOOO much stuff. With *(our_var_ptr)++ the inside of the parenthesis is evaluated first to our_var_ptr; which equals *our_var_ptr++.Mencius
@Betrothal *our_var_ptr = *our_var_ptr + 1; is the effect the original poster wanted; not what I was trying to explain. our_var_ptr = our_var_ptr + 1; is the reason why this while (*dst++ = *src++) ; works as a string copy in C.Mencius
S
8

As the others have pointed out, operator precedence cause the expression in the v2 function to be seen as *(our_var_ptr++).

However, since this is a post-increment operator, it's not quite true to say that it increments the pointer and then dereferences it. If this were true I don't think you'd be getting 63 as your output, since it would be returning the value in the next memory location. Actually, I believe the logical sequence of operations is:

  1. Save off the current value of the pointer
  2. Increment the pointer
  3. Dereference the pointer value saved in step 1

As htw explained, you aren't seeing the change to the value of the pointer because it is being passed by value to the function.

Seamus answered 13/5, 2009 at 19:13 Comment(1)
imo, this is bonkers. coming to the language with fresh eyes (I'm not), I would very much expect the incremented value to be dereferenced. Thanks for spelling out the situation here.Khan
U
8

Much confusion here, so here is a modified test program to make what happens clear (or at least clearer):

#include <stdio.h>

void add_one_v1(int *p){
  printf("v1: pre:   p = %p\n",p);
  printf("v1: pre:  *p = %d\n",*p);
    *p = *p + 1;
  printf("v1: post:  p = %p\n",p);
  printf("v1: post: *p = %d\n",*p);
}

void add_one_v2(int *p)
{
  printf("v2: pre:   p = %p\n",p);
  printf("v2: pre:  *p = %d\n",*p);
    int q = *p++;
  printf("v2: post:   p = %p\n",p);
  printf("v2: post:  *p = %d\n",*p);
  printf("v2: post:   q = %d\n",q);
}

int main()
{
  int ary[2] = {63, -63};
  int *ptr = ary;

    add_one_v1(ptr);         
    printf("@ %p\n", ptr);
    printf("%d\n", *(ptr));  
    printf("%d\n\n", *(ptr+1)); 

    add_one_v2(ptr);
    printf("@ %p\n", ptr);
    printf("%d\n", *ptr);
    printf("%d\n", *(ptr+1)); 
}

with the resulting output:

v1: pre:   p = 0xbfffecb4
v1: pre:  *p = 63
v1: post:  p = 0xbfffecb4
v1: post: *p = 64
@ 0xbfffecb4
64
-63

v2: pre:   p = 0xbfffecb4
v2: pre:  *p = 64
v2: post:  p = 0xbfffecb8
v2: post: *p = -63
v2: post:  q = 64

@ 0xbfffecb4
64
-63

Four things to note:

  1. changes to the local copy of the pointer are not reflected in the calling pointer.
  2. changes to the target of the local pointer do affect the target of the calling pointer (at least until the target pointer is updated)
  3. the value pointed to in add_one_v2 is not incremented and neither is the following value, but the pointer is
  4. the increment of the pointer in add_one_v2 happens after the dereference

Why?

  • Because ++ binds more tightly than * (as dereference or multiplication) so the increment in add_one_v2 applies to the pointer, and not what it points at.
  • post increments happen after the evaluation of the term, so the dereference gets the first value in the array (element 0).
Unharness answered 13/5, 2009 at 20:13 Comment(3)
Thanks for taking the time for writing this answer, it's this kind of stuff that makes StackOverflow so great an environment! I've had more insightful answers here in one hour than i would have found in the same time in my lousy C tutorials ;)Cinemascope
Thanks! I think this is the most important point - "Because ++ binds more tightly than *"Niobe
Also, folks should make note of this : int q = *p++; IS NOT EQUIVALENT TO *p++; *p++ will still have "first value" only. whereas in q it is "second value".( I think you mentioned it in second point)Niobe
C
5

If you are not using parenthesis to specify the order of operations, both prefix and postfix increments have precedence over reference and dereference. However, prefix increment and postfix increment are different operations. In ++x, the operator takes a reference to your variable, add one to it and return it by value. In x++, the operator increment your variable, but returns its old value. They behave sort of like this (imagine they are declared as methods inside your class):

//prefix increment (++x)
auto operator++()
{
    (*this) = (*this) + 1;
    return (*this);
}

//postfix increment (x++)
auto operator++(int) //unfortunately, the "int" is how they differentiate
{
    auto temp = (*this);
    (*this) = (*this) + 1; //same as ++(*this);
    return temp;
}

(Note that there is a copy involved in the postfix increment, making it is less efficient. That's the reason why you should prefer ++i instead of i++ in loops, even though most compilers do it automatically for you these days.)

As you can see, postfix increment is processed first, but, because of the way it behaves, you will be dereferencing the prior value of the pointer.

Here is an example:

char * x = {'a', 'c'};
char   y = *x++; //same as *(x++);
char   z = *x;

In the second line, the pointer x will be incremented before the dereference, but the dereference will happen over the old value of x (which is an address returned by the postfix increment). So y will be initialized with 'a', and z with 'c'. But if you do it like this:

char * x = {'a', 'c'};
char   y = (*x)++;
char   z = *x;

Here, x will be dereferenced and the value pointed by it ('a') will be incremented (to 'b'). Since the postfix increment returns the old value, y will still be initialized with 'a'. And since the pointer didn't change, z will be initialized with the new value 'b'.

Now let's check the prefix cases:

char * x = {'a', 'c'};
char   y = *++x; //same as *(++x)
char   z = *x;

Here, the dereference will happen on the incremented value of x (which is immediately returned by the prefix increment operator), so both y and z will be initialized with 'c'. To get a different behavior, you can change the order of the operators:

char * x = {'a', 'c'};
char   y = ++*x; //same as ++(*x)
char   z = *x;

Here you ensure that you are incrementing the content of x first and the value of the pointer never change, so y and z will be assigned with 'b'. In the strcpy function (mentioned in other answer), the increment is also done first:

char * strcpy(char * dst, char * src)
{
    char * aux = dst;
    while(*dst++ = *src++);
    return aux;
}

At each iteration, src++ is processed first and, being a postfix increment, it returns the old value of src. Then, the old value of src (which is a pointer) is dereferenced to be assigned to whatever is in the left side of the assignment operator. The dst is then incremented and its old value is dereferenced to become an lvalue and receive the old src value. This is why dst[0] = src[0], dst[1] = src[1] etc, up until *dst is assigned with 0, breaking the loop.

Addendum:

All the code in this answer was tested with C language. In C++ you probably won't be able to list-initialize the pointer. So, if you want to test the examples in C++, you should initialize an array first and then degrade it to a pointer:

char w[] = {'a', 'c'};
char * x = w;
char   y = *x++; //or the other cases
char   z = *x;
Carmencita answered 4/11, 2017 at 15:52 Comment(2)
char *x = {'a', 'c'}; char y = *++x; //or *(++x); char z = *x; y and z will be a or c?Viglione
@Viglione with 'c'. Thank you for pointing that! I fixed it in the answer.Carmencita
P
3

our_var_ptr is a pointer to some memory. ie it is the memory cell where the data is stored. (n this case 4 bytes in the binary format of an int).

*our_var_ptr is the dereferenced pointer - it goes to the location the pointer 'points' at.

++ increments a value.

so. *our_var_ptr = *our_var_ptr+1 dereferences the pointer and adds one to the value at that location.

Now add in operator precedence - read it as (*our_var_ptr) = (*our_var_ptr)+1 and you see that the dereference happens first, so you take the value and incremement it.

In your other example, the ++ operator has lower precedence than the *, so it takes the pointer you passed in, added one to it (so it points at garbage now), and then returns. (remember values are always passed by value in C, so when the function returns the original testvar pointer remains the same, you only changed the pointer inside the function).

My advice, when using dereferencing (or anything else) use brackets to make your decision explicit. Don't try to remember the precedence rules as you'll only end up using another language one day that has them slightly different and you'll get confused. Or old and end up forgetting which has higher precedence (like I do with * and ->).

Polychromatic answered 13/5, 2009 at 19:13 Comment(1)
Thanks for answering, i think the advice about making it more explicit by using brackets is solid!Cinemascope
F
2

I will try to answer this from a bit of a different angle... Step 1 Let's look at the operators and the operands: In this case it is the operand, and you have two operators, in this case * for dereferencing and ++ for increment. Step 2 which has the higher precedence ++ has higher precedence over * Step 3 Where is ++, it is to the right which means POST Increment In this case, the compiler take a 'mental note' to perform the increment AFTER it is done with all the other operators... note if it was *++p then it will do it BEFORE so in this case, it is as equivalent to taking two of the processor's register, one will hold the value of the dereferenced *p and the other will hold the value of the incremented p++, the reason in this case there are two, is the POST activity... This is where in this case it is tricky, and it looks like a contradiction. One would expect the ++ to take precedence over the *, which it does, only that the POST means that it will be applied only after ALL other operands, BEFORE the next ';' token...

Flagman answered 2/5, 2017 at 13:1 Comment(0)
B
1

From K&R, page 105: "The value of *t++ is the character that t pointed to before t was incremented".

Bicipital answered 2/11, 2017 at 6:37 Comment(0)
C
1
    uint32_t* test;
    test = &__STACK_TOP;


    for (i = 0; i < 10; i++) {
        *test++ = 0x5A5A5A5A;
    }

    //same as above

    for (i = 0; i < 10; i++) {
        *test = 0x5A5A5A5A;
        test++;
    }

Because test is a pointer, test++ (this is without dereferencing it) will increment the pointer (it increments the value of test which happens to be the (destination) address of what's being pointed at). Because the destination is of type uint32_t, test++ will increment by 4 bytes, and if the destination was for example an array of this type, then test would now be pointing at the next element. When doing these kinds of manipulations, sometimes you have to cast the pointer first to get the desired memory offset.

        ((unsigned char*) test)++;

This will increment the address by 1 byte only ;)

Chungchungking answered 13/6, 2018 at 12:50 Comment(0)
S
0

The '++' operator has higher precedence over the '*' operator, which means that the pointer address will be incremented before being dereferenced.

The '+' operator, however, has lower precedence than '*'.

Subscribe answered 13/5, 2009 at 19:4 Comment(1)
Ah! No. The ++ binds more tightly, so it is the pointer that is incremented. But it is the post-increment version, so it happens after the dereference. So: dereference the pointer and return a value, then increment the pointer (not the value pointed to).Unharness
P
0

Pictures are worth approximately a thousand words (give or take a million or so)...and symbols can be pictures (and vice versa.)

So⸺for those of us who seek tl;dr's (optimized data consumption) yet still want “(mostly) lossless” encoding, vector images/pictures/illustrations/demos are paramount.

In other words, just ignore my last 2 statements and see below.


Valid Forms

*a++ ≣ *(a++)
     ≣ (a++)[0] ≣ a++[0]
     ≣ 0[a++]  // Don't you dare use this (“educational purposes only”)
     // These produce equivalent (side) effects;
       ≡ val=*a,++a,val
       ≡ ptr=a,++a,*ptr
       ≡ *(ptr=a,++a,ptr)

*++a ≣ *(++a)
     ≣ *(a+=1)  ≣ *(a=a+1)
     ≣ (++a)[0] ≣ (a+=1)[0] ≣ (a=a+1)[0]  // ()'s are necessary
                ≣ 0[++a]   // 0[a+=1], etc... “educational purposes only”
     // These produce equivalent (side) effects:
       ≡ ++a,*a
       ≡ a+=1,*a
       ≡ a=a+1,*a

++*a ≣ ++(*a)
     ≣ *a+=1
     ≣ *a=*a+1
     ≣ ++a[0] ≣ ++(a[0])
              ≣ ++0[a]  // STAY AWAY

(*a)++  // Note that this does NOT return a pointer;
        // Location `a` points to does not change
        // (i.e. the address `a` is unchanged)
        ≡ val=*a,++*a,val

Notes

/* Indirection/deference operator must pr̲e̲cede the target identifier: */
a++*;
a*++;
++a*;
Pinery answered 8/7, 2019 at 1:39 Comment(0)
E
-1

Because the pointer is being passed in by value only the local copy gets incremented. If you really want to increment the pointer you have to pass it in by reference like this:

void inc_value_and_ptr(int **ptr)
{
   (**ptr)++;
   (*ptr)++;
}
Entophyte answered 17/8, 2012 at 14:41 Comment(1)
Doesn't actually answer the question. He was asking what *ptr++ does. Correct answer involves describing precedence.Hast

© 2022 - 2024 — McMap. All rights reserved.