student@ubuntu:~$
pointers-memory Lesson 9 10 min read

Double Pointers

Pointers to pointers -- modifying pointers from inside functions

Reading: Supplemental (not in textbook)

Quick check before you start: Can you write a function that modifies an int through 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 argv is char **
  • 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: *pp dereferences once – it gives you p (an address). **pp dereferences twice – it gives you x (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.


Check Your Understanding
Given: int x = 5; int *p = &x; int **pp = &p;
Which expression does NOT evaluate to 5?
Ax
B*p
C*pp
D**pp
Answer: C. *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.