Double pointers
We've seen that a pointer is a variable that stores a memory address. as such, a pointer has itself an address in memory.
A double pointer is a pointer that stores the address of another pointer. It's also commonly called a "pointer to pointer".
This concept extends the indirection principle we've already seen with regular pointers, adding another layer of reference.
Declaration
To declare a double pointer, use two asterisks before the variable name, and similar to regular pointers, a double pointer needs to be initialized before use:
int x = 10; // An integer variable
int *p = &x; // A pointer to x
int **pp = &p; // A double pointer to p
x
contains the value10
p
contains the address ofx
(&x
)pp
contains the address ofp
(&p
)
Visualizing double pointers
The relationship between variables, pointers, and double pointers can be visualized like this:
Alternative diagram with text:
&p=0xCAFEBABE p=0xFEEDBEEF *p=0xDEADBEEF <-- memory address
┌──────────────┐ ┌───────────────┐ ┌────────────────┐
│ p=0xFEEDBEEF │ -> │ *p=0xDEADBEEF │ -> │ **p=0x01234567 │ <-- memory content
└──────────────┘ └───────────────┘ └────────────────┘
&p
is the address ofp
, which is0xCAFEBABE
. The memory location0xCAFEBABE
stores the value ofp
, which is0xFEEDBEEF
. That is also the address of*p
.- The value of
p
is the address of*p
. - The value of
&p
is the address ofp
. - The value of
*p
is the address of**p
.
And so on. Note that *
and &
are like opposites, and *&p == p == &*p
. From here.
Dereferencing double pointers
With double pointers, you can dereference once or twice:
*pp
gives the value ofp
(which is the address ofx
)**pp
gives the value thatp
points to (which is the value ofx
)
int x = 10;
int *p = &x;
int **pp = &p;
printf("Value of x: %d\n", x); // Prints 10
printf("Value of *p: %d\n", *p); // Prints 10
printf("Value of *pp: %p\n", *pp); // Prints the address of x
printf("Value of **pp: %d\n", **pp); // Prints 10
// We can modify x through pp
**pp = 20;
printf("New value of x: %d\n", x); // Prints 20
Value of x: 10
Value of *p: 10
Value of *pp: 0xADDRESS
Value of **pp: 10
New value of x: 20
In this example, all three expressions x
, *p
, and **pp
refer to the same memory location, just with different levels of indirection.
Chain of references
Think of double pointers as a chain of references:
#include <stdio.h>
int main() {
int value = 42;
int *ptr = &value; // Points to value
int **dptr = &ptr; // Points to ptr
printf("Direct access: %d\n", value);
printf("Through pointer: %d\n", *ptr);
printf("Through double pointer: %d\n", **dptr);
// Change value through double pointer
**dptr = 100;
printf("New value: %d\n", value);
return 0;
}
Direct access: 42
Through pointer: 42
Through double pointer: 42
New value: 100
Double pointers in functions
One important use of double pointers is when you need to modify a pointer inside a function:
void allocate_int_pointer(int **pp) {
// Create a new int and make the original pointer point to it
int *temp = (int*)malloc(sizeof(int));
*temp = 42;
*pp = temp; // Modify where the original pointer points
}
int main() {
int *p = NULL;
allocate_int_pointer(&p); // Pass address of the pointer
printf("Value: %d\n", *p); // Prints 42
free(p); // Don't forget to free
return 0;
}
Without using a double pointer, we couldn't modify where the original pointer p
points to from within the function. This is because function parameters are passed by value in C, even when those parameters are pointers.
Practical use cases
Double pointers are commonly used for:
Modifying pointer values in functions (as shown above)
Arrays of strings (which are essentially arrays of character pointers):
char *names[] = {"Alice", "Bob", "Charlie"};
char **ptr = names; // ptr is a pointer to the first string pointer
printf("%s\n", *ptr); // Prints "Alice"
printf("%s\n", *(ptr+1)); // Prints "Bob"
Building data structures like linked lists where you need to modify head pointers
Creating 2D arrays dynamically
Many standard C library functions that need to modify string pointers use double pointers. For example, sorting an array of strings might use this pattern.
Common mistake: Mixing indirection levels
A common mistake is mixing up indirection levels:
int x = 10;
int *p = &x;
int **pp = &p;
printf("%d\n", **pp); // Correct: 10
printf("%d\n", *pp); // Wrong! This is an address, not an int
Remember that each level of indirection (*
) drops you down one level in the reference chain.
Sizing double pointers
Like all pointers, the size of a double pointer depends on your system architecture (typically 4 bytes on 32-bit systems and 8 bytes on 64-bit systems):
printf("Size of int: %zu bytes\n", sizeof(int));
printf("Size of int*: %zu bytes\n", sizeof(int*));
printf("Size of int**: %zu bytes\n", sizeof(int**));
On a 64-bit system, this would print:
Size of int: 4 bytes
Size of int*: 8 bytes
Size of int**: 8 bytes
Notice that both single and double pointers have the same size since they both store addresses.
Multilevel pointers
C doesn't limit you to just double pointers. You can create pointers with even more levels of indirection:
int x = 10;
int *p = &x; // Single pointer
int **pp = &p; // Double pointer
int ***ppp = &pp; // Triple pointer!
printf("%d\n", ***ppp); // Prints 10
While C allows any level of pointer indirection, using more than two levels is rare in practice. As indirection increases, code becomes harder to understand and maintain. Triple or higher-level pointers are usually a sign that you might want to reconsider your design.
Double pointers vs. multi-dimensional arrays
It's important not to confuse double pointers with 2D arrays, even though they can sometimes be used interchangeably.
A 2D array has contiguous memory allocation, while a double pointer can point to arrays that are scattered through memory.
For example:
int matrix[3][4]; // True 2D array - contiguous memory
int **dynamic_matrix; // Double pointer - can be used for 2D array
We'll explore this relationship further when we discuss dynamic memory allocation.