In C, a pointer to type T
points to a location where some data of type T
is stored. To keep things concrete, I will talk about T = int
below.
The most simple use of a pointer could be to point to one value:
int a = 42;
int *pa = &a;
Now, *pa
and a
are both the same, and equal to 42
. Also, *pa
and pa[0]
are both equivalent: so, for example, you can do:
*pa += 1; /* a is 43 */
pa[0] += 1; /* a is 44 */
a += 1; /* a is 45 */
In fact, the C compiler translates pa[0]
to *(pa+0)
automatically.
A pointer can point to a location that is inside a sequence of data:
int arr[] = { 1, 2, 3 }; /* 3 ints */
int *parr = arr; /* points to 1 */
Now, our memory looks like this:
+---+---+---+
arr: | 1 | 2 | 3 |
+---+---+---+
+------+ |
| parr | ----+
+------+
parr
is a pointer that points to the first element of arr
in the picture above. Incidentally, parr
also has a box around it, because we need to store the object parr
somewhere in the memory. The value of parr
is the address of the first element of arr
.
Now, we can use parr
to access the elements of arr
:
arr[0] == parr[0]; /* true */
parr[1]++; /* make arr[1] equal to 3 */
So, pointer can be used to mean, "I point to the first of n elements in a contiguous store of some objects". Of course, one has to know how many objects there are for this scheme to work: but once we remember to do so, it is an extremely convenient way to access memory in C.
Pointer can be made to point to dynamically allocated memory as well:
#include <stdlib.h>
size_t n;
/* now, obtain a value in n at runtime */
int *p = malloc(n * sizeof *p);
If the malloc()
call succeeds above, p
now points to the first of a contiguous area allocated for 10 int
s. We can use p[0]
through p[n-1]
in our program now.
You probably knew most or all of the above :-), but still, the above helps in understanding what I will say next.
Remember we said that a pointer can point to a contiguous sequence of objects of the same type? The "same type" can be another pointer type as well.
#include <stdlib.h>
int **pp;
pp = malloc(3 * sizeof *pp);
Now, pp
points to an int *
. Going back to our earlier picture:
+------+------+------+
| | | |
+------+------+------+
+------+ |
| pp | ----+
+------+
And each of the 3 boxes is an int *
, which can point to the first element of a contiguous sequence of int
s:
for (i=0; i < 3; ++i)
pp[i] = malloc((i + 1) * sizeof *ppi[i]);
Here, we allocated space for one int in pp[0]
, 2 in pp[1]
, and 3 in pp[3]
:
+------+ +---+
pp -------->| |-------->| |
+------+ +---+---+
| |-------->| | |
+------+ +---+---+---+
| |-------->| | | |
+------+ +---+---+---+
So, pp[0]
is a pointer to one int
, and that int
is the only int
in a dynamically allocated block of int
s. In other words, pp[0][0]
is an int
, and points to the top-most "width-3" box above. Similarly, pp[1][0]
and pp[1][1]
are both valid and are the two boxes below the pp[0][0]
box.
The most common use of pointer to pointer is to create a 2-dimensional "array" at runtime:
int **data;
size_t i;
data = malloc(n * sizeof *data);
for (i=0; i < n; ++i)
data[i] = malloc(m * sizeof *data[i]);
Now, assuming that all malloc()
s succeed, data[0]
...data[n-1]
are valid int *
values, each pointing to a separate length m
contiguous int
objects.
But, as I showed above, a pointer to pointer need not have the same "number of elements" in each of its "rows". The most obvious example is argv
in main()
.
By now, as you can guess, a "3-level deep" pointer, such as int ***p;
is okay and can be useful in C.