It appears that your actual question is this:
What's the use case for a pointer to a pointer?
A pointer to a pointer tends to show up when you have an array of some type T, and T itself is a pointer to something else. For example,
- What's a string in C? Typically, it's a
char *
.
- Would you like an array of strings from time to time? Sure.
- How would you declare one?
char *x[10]
: x
is an array of 10 pointers to char
, aka 10 strings.
At this point, you might be wondering where char **
comes in. It enters the picture from the very close relationship between pointers arithmetic and arrays in C. An array name, x
is (almost) always converted to a pointer to it's first element.
- What's the first element? A
char *
.
- What's a pointer to the first element? A
char **
.
In C, array E1[E2]
is defined to be equivalent to *(E1 + E2)
. Usually, E1
is the array name, let's say x
, which automatically converted to a char **
, and E2
is some index, say 3. (This rule also explains why 3[x]
and x[3]
are the same thing.)
Pointers to pointers also show up when you want a dynamically allocated array of some type T
, which is itself a pointer. To start with, let's pretend we don't know what type T is.
- If we want a dynamically allocated vector of T's, what type do we need?
T *vec
.
- Why? Because we can perform pointer arithmetic in C, any
T *
can serve as the base of a contiguous sequence of T
's in memory.
- How do we allocate this vector, say of
n
elements? vec = malloc(n * sizeof(T))
;
This story is true for absolutely any type T
, and so it's true for char *
.
- What's the type of
vec
if T
is char *
? char **vec
.
Pointers to pointers also show up when you have a function that needs to modify an argument of type T, itself a pointer.
- Look at the declaration for
strtol
: long strtol(char *s, char **endp, int b)
.
- What's this all about?
strtol
converts a string from base b
to an integer. It wants to tell you how far into the string it got. It could perhaps return a struct containing both a long
and a char *
, but that's not how it's declared.
- Instead, it returns its second result by passing in the address of a string which it modifies before returning.
- What's a string again? Oh yeah,
char *
.
- So what's an address of a string?
char **
.
If you wander down this path long enough, you can also run into T ***
types, although you can almost always restructure the code to avoid them.
Finally, pointers to pointers appear in certain tricky implementations of linked lists. Consider the standard declaration of a doubly-linked list in C.
struct node {
struct node *next;
struct node *prev;
/* ... */
} *head;
This works fine, although I won't reproduce the insertion/deletion functions here, but it has a little problem. Any node can be removed from the list (or have a new node inserted before it) without reference the head of the list. Well, not quite any node. This isn't true of the first element of the list, where prev
will be null. This can be moderately annoying in some kinds of C code where you work more with the nodes themselves than with the list as a concept. This is a reasonably common occurrence in low-level systems code.
What if we rewrite node
like this:
struct node {
struct node *next;
struct node **prevp;
/* ... */
} *head;
In each node, prevp
points not at the previous node, but at the previous nodes's next
pointer. What about the first node? It's prevp
points at head
. If you draw out a list like this (and you have to draw it out to understand how this works) you'll see that you can remove the first element or insert a new node before the first element without explicitly referencing head
by name.