So after you execute
double balance[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};
double *p;
double **m;
p = balance;
m = &p;
the following are all true:
m == &p // double **
*m == p == &balance[0] // double *
**m == *p == balance[0] // double
(*m)[i] == p[i] == balance[i] // double
Remember that the expression a[i]
is defined as *(a + i)
; given the address a
, offset i
elements (not bytes1) from that address and dereference the result.
This means that *p
is equivalent to *(p + 0)
, which is equivalent to p[0]
. Thus, you can use p[i]
in place of balance[i]
. Since *m == p
, you can also use (*m)[i]
in place of p[i]
. The parentheses are necessary - unary *
has lower precedence than postfix []
, so *m[i]
would be parsed as *(m[i])
, which is not what you want here.
You can increment p
directly to "walk" through the balance
array:
p = balance; // p == &balance[0];
for ( i = 0; i < 5; i++ )
printf( "%f\n", *p++ );
Each time through the loop, p
is incremented to point to the next element of balance
.
You can do something similar with the expression (*m)
:
p = balance; // p == &balance[0]
m = &p;
for ( i = 0; i < 5; i++ )
printf( "%f\n", (*m)++ );
Again, the parentheses around *m
are necessary; since postfix ++
has higher precedence than unary *
, the expression *m++
would be parsed as *(m++)
, which is not what we want. We don't want to change the value of m
, we want to change the value of what m
points to, which in this case is p
.
Now, suppose we leave p
out of the picture completely; can we do something like:
double balance[5] = { ... };
double **m;
*m = balance;
No. In this example, m
is an invalid pointer; it hasn't been initialized to point anywhere meaningful, so *m
will invoke undefined behavior (which can include, but is not limited to, a segfault). m
has to point to an object of type double *
before you can dereference it. There has to be a middleman like p
in order for that scheme to work.
- Pointer arithmetic always takes the size of the pointed-to type into account - if
a
points to an object of type T
, then a + 1
yields the address of the next object of type T
, which may be more than 1 byte away from the current address.