If you add these lines to the end of your program:
printf("%p: %p %p\n", ptr, ptr+1, ptr+2);
printf("%p: %p %p\n", *ptr, *ptr+1, *ptr+2);
printf("%p: %p %p\n", **ptr, **ptr+1, **ptr+2);
You will note that in the first case, the numbers increase by 8, which could either be the size of a pointer or two ints. Same goes for the second, but the third increases by the size of an int; so that is good.
So, to disambiguate 2 ints or 1 address, lets do a s/2/3/g.
Now, we see the first case the increment is now 12 ( = 3 * 4 ).
Your second case ( *ptr + i ) increments by 4, so is the address of successive ints
Your third case is the integer values themselves.
Where did it get confusing? Quick checklist:
When you are trying to workout pointer / indexing problems, use unique values as much as possible.
Pay attention when the compiler warns you. Eventually you will know to ignore "format '%p' expects argument ... ", but it takes time to build the confidence.
There is a handy program, cdecl, which converts C type expressions into something english like.
In virtually all C implementations, int x[2][2], y[4]; have the same layout; that is, C multi-dimensional arrays are just an overlay of a single dimension array, with the arithmetic cleverly hidden. For this reason, definitions like int (*p)[2]; are rare and rarely useful.
If you must go down this road, it is likely better to do something like:
typedef int pair[2];
pair torf[2] = { { 0, 1 }, { 2, 3 }};
pair *ptr = torf;
if nothing else, somebody has a chance to understand it...
ptrand indexithe expression*(ptr + i)is equal toptr[i]. That means**(ptr + 2)in your code is equal to*ptr[2], which is out of bounds.**(ptr + 2)- who'd know what it means. Just use the bracket notation with pointers. That stands for*ptr[2]which is much easier to make sense of.