31

I am trying my hands on a C pointer literature. In one of the illustrations, I encountered the following code.

# include <stdio.h>

int main()
{
     static int a[]={0,1,2,3,4};
     static int *p[]={a, a+1, a+2, a+3, a+4};

     int **ptr;

     ptr =p;

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

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

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

     return 0;
}

I receive the output as.

1 1 1
1 2 2
1 2 3

I am facing a problem in justifying this output. I made lot of boxes on a copy for easy grasp of the problem. I am able to justify the output 1 1 1, my trouble starts with the statement, *++*ptr.

Since, a unary operators are executed from right to left. So, *ptr would be tackled first, then the value at ptr would be incremented. After this increment, I am not sure what happens, the book says that somehow p is also incremented to point to the next element in this array. The output 1 2 2 can only be achieved through the increment of p.

I am not sure that this kind of question fits exactly on stackoverflow.
I tried my best, wasted at least 10 pages with boxes drawn over them.

Any clarification would be appreciated.

Grijesh Chauhan
  • 57,103
  • 20
  • 141
  • 208
Gaurav
  • 3,614
  • 3
  • 30
  • 51
  • Check the precedence rules for `*` and `++`. – Barmar Jul 19 '13 at 17:57
  • And compile with warnings on - there are some no-op dereferences in that code. – Carl Norum Jul 19 '13 at 17:58
  • 2
    @CarlNorum He's not printing pointers. He's printing the result of subtracting pointers, which are integers. – Barmar Jul 19 '13 at 17:58
  • 3
    My take (and C pointers drive me crazy) would be that, in `*++*ptr`, the value at `*ptr` would be pre-incremented, then that resulting pointer would be dereferenced to produce the expression value. But it's atrocious code -- it should never actually be written. – Hot Licks Jul 19 '13 at 17:58
  • 3
    @Barmar yup - comment deleted. He shouldn't use `%d` for that either, though. `%td` is the correct format string for a `ptrdiff_t`. – Carl Norum Jul 19 '13 at 17:58
  • Point taken. %d won't print the address here. If we subtract two pointers assuming they are of same type, all we would get is difference between their memory locations divided by their data type size. – Gaurav Jul 19 '13 at 17:59

4 Answers4

54

Remember array name can easily decays into pointer to first element in most expressions (read some exceptions where array name not decaying into a pointer to first element? ably answered by @H2CO3).
For better understanding, consider my diagrams:

First, suppose a stored in memory as follows.

  a 
+----+----+----+----+---+
|  0 |  1 | 2  | 3  | 4 |
+----+----+----+----+---+
  ▲    ▲    ▲    ▲    ▲
  |    |    |    |    | 
  a    a+1  a+2  a+3  a+3

Declaration static int *p[] = {a, a+1, a+2, a+3, a+4}; creates a new array of pointers to integer, with following values:

p[0] == a
p[1] == a + 1
p[2] == a + 2
p[3] == a + 3
p[4] == a + 4

Now, p can also be assume to be stored in memory something like below:

  p
+----+----+----+----+-----+
| a  |a +1| a+2| a+3| a+4 | 
+----+----+----+----+-----+
  ▲    ▲    ▲    ▲    ▲
  |    |    |    |    |
  p    p+1  p+2  p+3  p+4

After assignment ptr = p; things will be something like this:

  p                              a 
+----+----+----+----+-----+    +----+----+----+----+---+
| a  |a +1| a+2| a+3| a+4 |    |  0 |  1 | 2  | 3  | 4 |
+----+----+----+----+-----+    +----+----+----+----+---+
  ▲    ▲    ▲    ▲    ▲          ▲    ▲    ▲    ▲    ▲
  |    |    |    |    |          |    |    |    |    | 
  p    p+1  p+2  p+3  p+4        a    a+1  a+2  a+3  a+3
  ptr 


Notice: ptr points to first location in pointer array p[]

Expression: **ptr++;

Now we consider expression **ptr++; before first printf statement.

  1. ptr is equals to p that is address of first element in array of pointers. Hence, ptr point to first element p[0] in array (or we can say ptr == &p[0]).

  2. *ptr means p[0] and because p[0] is a, so *ptr is a ( so *ptr == a).

  3. And because *ptr is a, then **ptr is *a == *(a + 0) == a[0] that is 0.

  4. Note in expression **ptr++;, we do not assign its value to any lhs variable.
    So effect of **ptr++; is simply same as ptr++; == ptr = ptr + 1 = p + 1
    In this way after this expression ptr pointing to p[1] (or we can say ptr == &p[1]).

Print-1:

Before first printf things become:

  p                              a 
+----+----+----+----+-----+    +----+----+----+----+---+
| a  | a+1| a+2| a+3| a+4 |    |  0 |  1 | 2  | 3  | 4 |
+----+----+----+----+-----+    +----+----+----+----+---+
  ▲    ▲    ▲    ▲    ▲          ▲    ▲    ▲    ▲    ▲
  |    |    |    |    |          |    |    |    |    | 
  p    p+1  p+2  p+3  p+4        a    a+1  a+2  a+3  a+3
       ptr 


Notice: ptr is equals to  p + 1 that means it points to p[1]

Now we can understand First printf:

  1. ptr - p output 1 because:
    ptr = p + 1, so ptr - p == p + 1 - p == 1

  2. *ptr - a output 1 because:
    ptr = p + 1, so *ptr == *(p + 1) == p[1] == a + 1
    This means: *ptr - a = a + 1 - a == 1

  3. **ptr output 1 because:
    *ptr == a + 1 from point-2
    So **ptr == *(a + 1) == a[1] == 1

Expression: *++*ptr;

After first printf we have an expression *++*ptr;.

As we know from above point-2 that *ptr == p[1]. So, ++*ptr (that is ++p[1]) will increments p[1] to a + 2

Again understand, in expression *++*ptr; we don't assign its value to any lhs variable so effect of *++*ptr; is just ++*ptr;.

Now, before second printf things become:

  p                              a 
+----+----+----+----+-----+    +----+----+----+----+---+
| a  |a+2 | a+2| a+3| a+4 |    |  0 |  1 | 2  | 3  | 4 |
+----+----+----+----+-----+    +----+----+----+----+---+
  ▲    ▲    ▲    ▲    ▲          ▲    ▲    ▲    ▲    ▲
  |    |    |    |    |          |    |    |    |    | 
  p    p+1  p+2  p+3  p+4        a    a+1  a+2  a+3  a+3
       ptr 


Notice: p[1] became a + 2 

Print-2:

Now we can understand Second printf:

  1. ptr - p output 1 because:
    ptr = p + 1, so ptr - p == p + 1 - p == 1

  2. *ptr - a output 2 because:
    ptr = p + 1 so *ptr == *(p + 1) == p[1] == a + 2
    This means: *ptr - a == a + 2 - a == 2

  3. **ptr output 2 because:
    *ptr == a + 2 from point-2
    So **ptr == *(a + 2) == a[2] == 2

Expression: ++**ptr;

Now expression ++**ptr; before third printf.

As we know from above point-3 that **ptr == a[2]. So ++**ptr == ++a[2] will increments a[2] to 3

So before third printf things become:

  p                              a 
+----+----+----+----+-----+    +----+----+----+----+---+
| a  | a+2| a+2| a+3| a+4 |    |  0 |  1 | 3  | 3  | 4 |
+----+----+----+----+-----+    +----+----+----+----+---+
  ▲    ▲    ▲    ▲    ▲          ▲    ▲    ▲    ▲    ▲
  |    |    |    |    |          |    |    |    |    | 
  p    p+1  p+2  p+3  p+4        a    a+1  a+2  a+3  a+3
       ptr 


 Notice: a[2] = 3

Print-3:

Now we can understand Third printf:

  1. ptr - p output 1 because:
    ptr = p + 1 so ptr - p == p + 1 - p == 1

  2. *ptr - a output 2 because:
    ptr = p + 1 so *ptr == *(p + 1) == p[1] == a + 2
    This means: *ptr - a = a + 2 - a == 2

  3. **ptr outputs 3 because:
    *ptr == a + 2 from point-2
    So **ptr == *(a + 2) == a[2] == 3

Edit Note: The difference of two pointers has type ptrdiff_t, and for that, the correct conversion specifier is %td, not %d.

An additional point:
I wish to add as I believe it will be helpful for new learners

Suppose we have following two lines with one more 4th printf in you code before return 0;

**++ptr;    // additional 
printf("%d %d %d\n", ptr-p, *ptr-a, **ptr);  // fourth printf

One can check this working code @Codepade , this line outputs 2 2 3.

Expression: **++ptr;

Because ptr is equals to p + 1 , after increment ++ operation ptr becomes p + 2 (or we can say ptr == &p[2]).
After that double deference operation ** ==> **(p + 2) == *p[2] == *(a + 2) == a[2] == 3.
Now, again because we don't have any assignment operation in this statement so effect of expression **++ptr; is just ++ptr;.

So thing after expression **++ptr; becomes as below in figure:

  p                              a 
+----+----+----+----+-----+    +----+----+----+----+---+
| a  | a+2| a+2| a+3| a+4 |    |  0 |  1 | 3  | 3  | 4 |
+----+----+----+----+-----+    +----+----+----+----+---+
  ▲    ▲    ▲    ▲    ▲          ▲    ▲    ▲    ▲    ▲
  |    |    |    |    |          |    |    |    |    | 
  p    p+1  p+2  p+3  p+4        a    a+1  a+2  a+3  a+3
            ptr 

 Notice: ptr is equals to  p + 2 that means it points to p[2] 

Print-4:

Considering Forth printf I added in question:

  1. ptr - p output 2 because:
    ptr = p + 2 so ptr - p == p + 2 - p == 2

  2. *ptr - a output 2 because:
    ptr = p + 2 so *ptr == *(p + 2) == p[2] == a + 2
    This means: *ptr - a = a + 2 - a == 2

  3. **ptr outputs 3 because:
    *ptr == a + 2 from above point-2
    So **ptr == *(a + 2) == a[2] == 3

Community
  • 1
  • 1
Grijesh Chauhan
  • 57,103
  • 20
  • 141
  • 208
  • It doesn't explains the second line of output. – Gaurav Jul 19 '13 at 18:16
  • 2
    it does explains everything. Accepted answer. Thanks. I would like to be in touch with you. – Gaurav Jul 19 '13 at 21:09
  • 4
    This excellent answer makes me realize that I probably never will understand C on a deeper level ;) – jpw Jul 19 '13 at 22:16
  • 2
    @GrijeshChauhan Your answer is excellent. It explains everything. I wish I could up-vote it more than once. – Gaurav Jul 20 '13 at 04:41
  • Very excellent explanation! I'm losing hope to figure it out myself till I see this :) – mr5 Aug 31 '13 at 08:33
8

If you compile with some warnings on (clang didn't even require any flags), you'll see that your program has three extraneous * operators. Simplifying your crazy-looking expressions yields:

ptr++;
++*ptr;
++**ptr;

And from that, you should be able to see what's happening pretty clearly:

  1. ptr++ just increments ptr, so it points to the second element of p. After this operation ptr - p will always be 1.

  2. ++*ptr increments whatever is pointed to by ptr. That changes the second element of p to point to the third element of a rather than the second (which it was initialized to). That makes *ptr - a equal to 2. Likewise **ptr is the 2 from a.

  3. ++**ptr increments whatever is pointed to by whatever is pointed to by ptr. That increments the third element of a, making it a 3.

Carl Norum
  • 219,201
  • 40
  • 422
  • 469
2

Remember that ++ has higher precedence than *, so when you do **ptr++, that increments the pointer and double-dereferences the old value, which will do nothing more than crash if it's not a valid pointer-to-pointer, and your compiler (at least with warnings enabled) should warn you of an unused result.

 static int a[]={0,1,2,3,4};

 static int *p[]={a, a+1, a+2, a+3, a+4};

 int **ptr;

 ptr = p; // ptr = &(p[0]); *ptr = a; **ptr = 0.

 **ptr++; // ptr = &(p[1]); *ptr = a+1; **ptr = 1
 printf("%d %d %d\n", ptr-p, *ptr-a, **ptr);

 *++*ptr; // ptr = &(p[1]); *ptr = a+2; **ptr = 2; p = {a, a+2, a+2, a+3, a+4} 
 printf("%d %d %d\n", ptr-p, *ptr-a, **ptr);

 ++**ptr; // ptr = &(p[1]); *ptr = a+2; **ptr = 3; a = {0, 1, 3, 3, 4}
 printf("%d %d %d\n", ptr-p, *ptr-a, **ptr);
Kevin
  • 53,822
  • 15
  • 101
  • 132
  • Kevin, in your answer: *`so when you do **ptr++, that increments the pointer and double-dereferences it,`* is not correct! [its confusing to me as well](http://stackoverflow.com/questions/17752549/pointer-ptr-use/17752682#comment25892011_17752549) if I understand it by precedence rules. But it doesn't work like this. Its first deference (`*`) then `=` then `++` (because `++` is postfix operator). [Check this working code](http://codepad.org/m6zAaPkg) – Grijesh Chauhan Jul 20 '13 at 13:48
  • 1
    Right, it would double-dereference the old value. I've clarified. – Kevin Jul 20 '13 at 13:54
2

The int* value at address ptr has been incremented by the statement *++*ptr; (actually by the ++*ptr portion, the leading * is an unused dereference). Thus the expansion of int *p[] should now look like this:

int *p[]={a, a+2, a+2, a+3, a+4};

The final ++**ptr; has now incremented the value at address a+2, so the original array will now look like this:

int a[]={0,1,3,3,4};

abiessu
  • 1,907
  • 1
  • 18
  • 16