Pointers to pointers
Work with double pointers (int **) in C — understanding argv, functions that modify pointer variables, and multi-level indirection.
- Declare and use an int ** variable
- Explain why a pointer to a pointer is needed to modify a pointer from a function
- Interpret the argv parameter of main as a char **
- Trace three levels of indirection through a diagram
A pointer to a pointer is exactly what it sounds like: a variable that holds the address of another pointer. Double pointers (**) come up in three main situations: when a function needs to modify a pointer variable in its caller, when representing arrays of strings (like argv), and when building dynamic data structures like linked lists and trees.
Why you need a pointer to a pointer
Consider: you want a function that sets a pointer to point somewhere new. The naive approach fails:
#include <stdio.h>
void set_to_five(int *p, int *new_target) {
p = new_target; /* modifies p (a local copy of the pointer) -- caller's pointer unchanged */
}
int main(void) {
int x = 42;
int *ptr = &x;
int five = 5;
set_to_five(ptr, &five);
printf("%d\n", *ptr); /* still 42 -- ptr was not changed */
return 0;
}ptr is passed by value — the function gets a copy of the pointer. Reassigning p inside the function does not change ptr in main.
To modify ptr from a function, pass its address (&ptr) — a pointer to the pointer:
void set_pointer(int **pp, int *new_target) {
*pp = new_target; /* write a new address into the pointer pp points to */
}
int main(void) {
int x = 42;
int *ptr = &x;
int five = 5;
set_pointer(&ptr, &five); /* pass address of ptr */
printf("%d\n", *ptr); /* 5 -- ptr now points to five */
return 0;
}pp holds the address of ptr. *pp = new_target writes new_target into ptr.
argv — the classic double pointer
The main function's full signature:
int main(int argc, char **argv) {argv is a char **: a pointer to the first element of an array of char * pointers, each of which points to a command-line argument string.
argv ──► [ptr0] ──► "program_name\0"
[ptr1] ──► "first_arg\0"
[ptr2] ──► "second_arg\0"
[NULL]#include <stdio.h>
int main(int argc, char **argv) {
printf("Program: %s\n", argv[0]);
printf("Arguments:\n");
for (int i = 1; i < argc; i++) {
printf(" argv[%d] = \"%s\"\n", i, argv[i]);
}
return 0;
}Run as ./program hello world prints:
Program: ./program
Arguments:
argv[1] = "hello"
argv[2] = "world"argv[argc] is guaranteed to be NULL — you can also iterate as while (*argv) by incrementing the pointer.
Three levels: int ***
Triple pointers are rare but arise in certain data structures. The key is to work level by level:
int value = 42;
int *p1 = &value; /* points to value */
int **p2 = &p1; /* points to p1 */
int ***p3 = &p2; /* points to p2 */
printf("%d\n", ***p3); /* 42 -- dereference three times */A practical example: allocate inside a function
Dynamic memory allocation (covered in the next module) often requires double pointers:
#include <stdio.h>
#include <stdlib.h>
void allocate_array(int **arr, int n) {
*arr = malloc(n * sizeof(int));
if (*arr == NULL) { return; }
for (int i = 0; i < n; i++) {
(*arr)[i] = i * 10;
}
}
int main(void) {
int *data = NULL;
allocate_array(&data, 5);
if (data != NULL) {
for (int i = 0; i < 5; i++) {
printf("%d ", data[i]); /* 0 10 20 30 40 */
}
printf("\n");
free(data);
}
return 0;
}malloc is introduced properly in the Memory Management module. For now, notice that the function receives &data (a int **) and writes the allocated pointer into *arr, which modifies data in main.
Where to go next
Next: const pointers — how const interacts with pointers, and the difference between a pointer to const data and a const pointer.
Pointers and arrays
Understand the deep relationship between pointers and arrays in C — how array names decay to pointers, why sizeof behaves differently, and how to write generic array functions.
const pointers
Learn how const interacts with pointers in C — the distinction between pointer to const data, const pointer, and const pointer to const data.