Double Pointers
Pointers to pointers -- modifying pointers from inside functions
Quick check before you start: Can you write a function that modifies an
intthrough a pointer parameter? If not, review Pass-by-Pointer first.Practice this topic: Double Pointers skill drill
After this lesson, you will be able to:
- Declare and use
int **(pointer-to-pointer) variables - Apply the rule of stars: dereference from the outside in
- Write a function that modifies a caller’s pointer through a double pointer parameter
- Explain why
argvischar ** - Trace code involving two levels of indirection using memory diagrams
When One Star Is Not Enough
You know that to modify an int from inside a function, you pass an int *. But what if you want to modify a pointer from inside a function? You need a pointer to that pointer – an int **.
This comes up when a function needs to allocate memory and hand back the pointer, or when you build data structures like linked lists where you modify where a pointer points.
The Rule of Stars
To modify a variable of type T from inside a function, pass a T *. Apply this rule recursively:
| Want to modify | Pass | Function parameter |
|---|---|---|
int x |
&x |
int *p |
int *p |
&p |
int **pp |
The pattern is clean and uniform.
A Concrete Example
void allocate_array(int **arr_ptr, int size)
{
*arr_ptr = calloc(size, sizeof(int)); /* Modifies caller's pointer */
}
int main(void)
{
int *data = NULL;
allocate_array(&data, 10); /* Pass address of the pointer */
/* data now points to allocated memory */
data[0] = 42;
printf("%d\n", data[0]); /* 42 */
free(data);
return 0;
}
Memory diagram:
main: allocate_array:
+------+ +---------+
| data | --> [heap memory] | arr_ptr | --> data (in main)
+------+ +---------+
*arr_ptr = calloc(...) -> changes where 'data' points
Why a Single Pointer Does Not Work
void broken_allocate(int *arr, int size)
{
arr = calloc(size, sizeof(int)); /* Changes LOCAL copy of pointer */
/* Caller's pointer is unchanged! */
}
This modifies the function’s copy of the pointer – the caller’s pointer stays NULL. The same pass-by-value rule from the previous lesson applies: arr is a copy, so assigning to it is useless.
Reading Double Pointer Code
Trace through this step by step:
int x = 5;
int *p = &x;
int **pp = &p;
printf("%d\n", x); /* 5 */
printf("%d\n", *p); /* 5 (follow one arrow) */
printf("%d\n", **pp); /* 5 (follow two arrows) */
**pp = 99; /* Changes x through two levels */
printf("%d\n", x); /* 99 */
+----+ +---+ +---+
| pp | --> | p | --> | x | = 99
+----+ +---+ +---+
Key Insight:
*ppdereferences once – it gives youp(an address).**ppdereferences twice – it gives youx(the value). One star peels off one level of indirection.
Modifying a Pointer Through a Function
This is the central use case. Compare:
/* BROKEN: changes local copy of head */
void set_null_broken(int *ptr)
{
ptr = NULL; /* Useless -- local copy */
}
/* WORKING: changes caller's pointer */
void set_null(int **ptr_addr)
{
*ptr_addr = NULL; /* Dereference once to reach caller's pointer */
}
int main(void)
{
int val = 42;
int *p = &val;
set_null(&p); /* Pass address of the pointer */
/* p is now NULL */
return 0;
}
Dynamic 2D Arrays with int**
A 2D array can be built as an array of pointers, each pointing to a row:
int rows = 3, cols = 4;
int **matrix = calloc(rows, sizeof(int *));
for (int i = 0; i < rows; i++)
{
matrix[i] = calloc(cols, sizeof(int));
}
matrix[1][2] = 99; /* Access like a 2D array */
/* Free in reverse order */
for (int i = 0; i < rows; i++)
{
free(matrix[i]);
}
free(matrix);
matrix is int ** – a pointer to an array of int * row pointers.
argv Is char**
The main function signature you have been using:
int main(int argc, char *argv[])
is equivalent to:
int main(int argc, char **argv)
argv is a pointer to an array of char * (string pointers). argv[0] is the program name, argv[1] is the first argument, and so on. This is a double pointer in action.
int x = 5; int *p = &x; int **pp = &p;Which expression does NOT evaluate to 5?
*pp dereferences once -- it follows pp to get p, which is an address (like 0x1000), not the value 5. You need **pp to follow both arrows and reach x. One star gets you the inner pointer, two stars get you the final value.
What Comes Next
The pattern is clean: to modify a T from inside a function, pass a T *. It does not matter what T is – int, int *, struct Node * – the rule applies uniformly. Double pointers are just the same pattern applied twice.
Next: memory layout. You will learn where variables live (stack, heap, data, text) and how calloc and free manage dynamic memory.