Nearby lessons

94 of 124

C - Pointer to Pointer

A pointer to a pointer stores the address of another pointer. Learn the ** declaration, double dereferencing, and the two places it genuinely matters — modifying a caller's pointer and building dynamic 2D arrays.

Three Levels of Indirection

A normal pointer points at data. A pointer to a pointer points at a pointer:

ExpressionHoldsType
xThe value 42int
pThe address of xint *
ppThe address of pint **
*ppThe address of xint *
**ppThe value 42int
Example01
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 int x = 42;
6 int *p = &x; /* p points to x */
7 int **pp = &p; /* pp points to p */
8 
9 printf("x = %d\n", x);
10 printf("*p = %d\n", *p);
11 printf("**pp = %d\n", **pp);
12 
13 printf("\n&x = %p\n", (void *) &x);
14 printf("p = %p\n", (void *) p);
15 printf("*pp = %p (same as p)\n", (void *) *pp);
16 printf("&p = %p\n", (void *) &p);
17 printf("pp = %p (same as &p)\n", (void *) pp);
18 return 0;
19}
Output
x    = 42
*p   = 42
**pp = 42

&x  = 0x7ffd1c2a3b4c
p    = 0x7ffd1c2a3b4c
*pp  = 0x7ffd1c2a3b4c  (same as p)
&p   = 0x7ffd1c2a3b50
pp   = 0x7ffd1c2a3b50  (same as &p)

Writing Through Two Levels

**pp = value reaches all the way down to x. *pp = address changes p instead:

Example02
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 int x = 10, y = 99;
6 int *p = &x;
7 int **pp = &p;
8 
9 **pp = 50; /* changes x */
10 printf("After **pp = 50 : x = %d\n", x);
11 
12 *pp = &y; /* changes p to point at y */
13 printf("After *pp = &y : *p = %d, **pp = %d\n", *p, **pp);
14 printf("x is untouched : %d\n", x);
15 return 0;
16}
Output
After **pp = 50 : x = 50
After *pp = &y  : *p = 99, **pp = 99
x is untouched  : 50

Why It Matters — Modifying a Caller's Pointer

In simple words: a function that takes int * can change the value the caller's pointer points to. To change which address the caller's pointer holds, the function needs the address of the pointer itself — that is int **.
Example03
CCode Cell
1#include <stdio.h>
2 
3int a = 100, b = 200;
4 
5void brokenRepoint(int *p) { p = &b; } /* changes the local copy */
6void workingRepoint(int **p){ *p = &b; } /* changes the caller's pointer */
7 
8int main()
9{
10 int *ptr = &a;
11 
12 brokenRepoint(ptr);
13 printf("After broken : *ptr = %d\n", *ptr);
14 
15 workingRepoint(&ptr);
16 printf("After working: *ptr = %d\n", *ptr);
17 return 0;
18}
Output
After broken : *ptr = 100
After working: *ptr = 200

The Real-World Case — Allocating in a Function

This is the pattern that makes ** unavoidable: a function that allocates memory and hands the pointer back to the caller through a parameter:

Example04
CCode Cell
1#include <stdio.h>
2#include <stdlib.h>
3 
4int allocateArray(int **arr, int n)
5{
6 *arr = malloc(n * sizeof(int)); /* writes the caller's pointer */
7 if (*arr == NULL) return 0;
8 
9 for (int i = 0; i < n; i++)
10 (*arr)[i] = (i + 1) * 10;
11 return 1;
12}
13 
14int main()
15{
16 int *numbers = NULL;
17 int i;
18 
19 if (allocateArray(&numbers, 5)) /* pass the pointer's address */
20 {
21 for (i = 0; i < 5; i++) printf("%d ", numbers[i]);
22 printf("\n");
23 free(numbers);
24 }
25 return 0;
26}
Output
10 20 30 40 50 

Freeing Safely Through **

A helper that frees and sets the caller's pointer to NULL in one step — impossible with a single level of indirection:

Example05
CCode Cell
1#include <stdio.h>
2#include <stdlib.h>
3 
4void safeFree(void **p)
5{
6 if (p != NULL && *p != NULL)
7 {
8 free(*p);
9 *p = NULL; /* the caller's pointer is now NULL */
10 }
11}
12 
13int main()
14{
15 int *data = malloc(sizeof(int));
16 if (data == NULL) return 1;
17 
18 *data = 42;
19 printf("Before: *data = %d\n", *data);
20 
21 safeFree((void **) &data);
22 printf("After : data is %s\n", data ? "still valid" : "NULL");
23 return 0;
24}
Output
Before: *data = 42
After : data is NULL

A Dynamic 2D Array

An int ** holds an array of row pointers, each pointing at its own row. This is how you build a matrix whose size is decided at runtime:

Example06
CCode Cell
1#include <stdio.h>
2#include <stdlib.h>
3 
4int main()
5{
6 int rows = 3, cols = 4, i, j;
7 
8 /* 1. an array of row pointers */
9 int **matrix = malloc(rows * sizeof(int *));
10 if (matrix == NULL) return 1;
11 
12 /* 2. a row for each pointer */
13 for (i = 0; i < rows; i++)
14 {
15 matrix[i] = malloc(cols * sizeof(int));
16 if (matrix[i] == NULL) return 1;
17 }
18 
19 for (i = 0; i < rows; i++)
20 for (j = 0; j < cols; j++)
21 matrix[i][j] = (i + 1) * (j + 1);
22 
23 for (i = 0; i < rows; i++)
24 {
25 for (j = 0; j < cols; j++) printf("%4d", matrix[i][j]);
26 printf("\n");
27 }
28 
29 /* 3. free in reverse order */
30 for (i = 0; i < rows; i++) free(matrix[i]);
31 free(matrix);
32 return 0;
33}
Output
   1   2   3   4
   2   4   6   8
   3   6   9  12

Freeing in the Right Order

Free the rows before the row array. If you free(matrix) first, every row pointer is lost and those blocks can never be released — a guaranteed memory leak. Rows first, then the outer array, always.
Example07
CCode Cell
1/* WRONG - the row pointers are gone before we can use them
2free(matrix);
3for (i = 0; i < rows; i++) free(matrix[i]); <- reading freed memory
4*/
5 
6/* RIGHT - inside out */
7for (i = 0; i < rows; i++)
8 free(matrix[i]);
9free(matrix);
10matrix = NULL;
Output
No leaks, no use-after-free

An Array of Strings

char ** is the natural type for a list of strings — and exactly what main's argv is:

Example08
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 const char *names[3] = {"Rahul", "Priya", "Amit"};
6 const char **p = names; /* pointer to the first pointer */
7 int i;
8 
9 for (i = 0; i < 3; i++)
10 printf("names[%d] = %s\n", i, p[i]);
11 
12 printf("\nFirst letter of the second name: %c\n", p[1][0]);
13 printf("Same thing: %c\n", *(*(p + 1)));
14 return 0;
15}
Output
names[0] = Rahul
names[1] = Priya
names[2] = Amit

First letter of the second name: P
Same thing: P

argv Is a char **

These two signatures for main are equivalent — char *argv[] as a parameter is char **argv:

Example09
CCode Cell
1#include <stdio.h>
2 
3int main(int argc, char **argv) /* same as char *argv[] */
4{
5 int i;
6 
7 for (i = 0; i < argc; i++)
8 printf("argv[%d] = %s\n", i, argv[i]);
9 
10 if (argc > 1)
11 printf("First char of argv[1]: %c\n", argv[1][0]);
12 return 0;
13}
14 
15/* Run as: ./program hello world */
Output
argv[0] = ./program
argv[1] = hello
argv[2] = world
First char of argv[1]: h

Common Mistakes

  • Dereferencing the wrong number of times*pp is a pointer, **pp is the value.
  • Passing ptr where &ptr is needed — the function then cannot repoint the caller's pointer.
  • Forgetting the parentheses*arr[i] is not (*arr)[i].
  • Freeing the outer array first — leaks every row.
  • Not checking each row's malloc — one failure mid-loop leaves a half-built matrix.
  • Assuming a dynamic 2D array is contiguous — the rows are separate blocks, so matrix[0][cols] is out of bounds, not matrix[1][0].
Trainer's Note: count your asterisks by counting your questions. "Where is the value?" → one *. "Where is the pointer that knows where the value is?" → two. If a function must change something the caller can see, it needs one more level of indirection than the thing being changed.
📝 Key Takeaways
  • Syntax: int **pp; — pp holds the address of an int *.
  • *pp is the inner pointer; **pp is the actual value.
  • To change a caller's pointer, a function needs ** .
  • char *argv[] in main is effectively a char ** .
  • A dynamic 2D array is an array of row pointers.

🧠 Test Your Knowledge

4 Questions
Progress: 0 / 4