README
Pointer to Pointer (Double Pointers)
Table of Contents
- •Introduction
- •What is a Pointer to Pointer?
- •Declaration and Initialization
- •Memory Layout and Visualization
- •Dereferencing Double Pointers
- •Triple and N-Level Pointers
- •Practical Applications
- •Double Pointers with Arrays
- •Dynamic 2D Arrays
- •Command Line Arguments
- •Common Mistakes and Pitfalls
- •Best Practices
- •Summary
Introduction
A pointer to pointer (also called a double pointer) is a variable that stores the address of another pointer variable. Just as a regular pointer holds the address of a normal variable, a double pointer holds the address of a pointer.
Why Do We Need Double Pointers?
- •Modify pointers in functions: Pass pointer by reference to change what it points to
- •Dynamic 2D arrays: Create and manage multi-dimensional arrays at runtime
- •Array of strings: Handle collections of strings efficiently
- •Command-line arguments: Process
argvin main function - •Complex data structures: Implement linked lists, trees, and graphs
What is a Pointer to Pointer?
Concept
Level 0: Variable → Holds actual data
Level 1: Pointer → Holds address of variable
Level 2: Double Pointer → Holds address of pointer
Visual Representation
+--------+ +--------+ +--------+
| 2000 | ---> | 1000 | ---> | 42 |
+--------+ +--------+ +--------+
pp ptr var
(double ptr) (pointer) (integer)
pp stores address of ptr (2000)
ptr stores address of var (1000)
var stores the value 42
The Chain of References
int var = 42; // A regular variable
int *ptr = &var; // Pointer holding address of var
int **pp = &ptr; // Double pointer holding address of ptr
// Accessing the value:
var → 42 (direct access)
*ptr → 42 (one dereference)
**pp → 42 (two dereferences)
Declaration and Initialization
Syntax
datatype **pointer_name;
The ** indicates it's a pointer to a pointer.
Step-by-Step Declaration
// Step 1: Declare a normal variable
int number = 100;
// Step 2: Declare a pointer and point to the variable
int *ptr = &number;
// Step 3: Declare a double pointer and point to the pointer
int **pp = &ptr;
Complete Example
#include <stdio.h>
int main() {
int value = 50;
int *single_ptr = &value;
int **double_ptr = &single_ptr;
printf("value = %d\n", value);
printf("&value = %p\n", (void*)&value);
printf("\nsingle_ptr = %p (stores &value)\n", (void*)single_ptr);
printf("*single_ptr = %d (dereferenced)\n", *single_ptr);
printf("&single_ptr = %p\n", (void*)&single_ptr);
printf("\ndouble_ptr = %p (stores &single_ptr)\n", (void*)double_ptr);
printf("*double_ptr = %p (single_ptr value)\n", (void*)*double_ptr);
printf("**double_ptr = %d (value)\n", **double_ptr);
return 0;
}
Different Data Types
// Integer double pointer
int **int_pp;
// Character double pointer (common for string arrays)
char **char_pp;
// Float double pointer
float **float_pp;
// Double double pointer
double **double_pp;
// Void double pointer
void **void_pp;
Memory Layout and Visualization
Understanding the Memory Model
Let's trace through memory with concrete addresses:
int x = 10; // x at address 0x1000
int *p = &x; // p at address 0x2000, stores 0x1000
int **pp = &p; // pp at address 0x3000, stores 0x2000
Memory Address Variable Value Meaning
──────────────────────────────────────────────────────────
0x3000 pp 0x2000 Address of p
0x2000 p 0x1000 Address of x
0x1000 x 10 The actual data
Visual Memory Map
Address: 0x3000 0x2000 0x1000
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 0x2000 │───►│ 0x1000 │───►│ 10 │
└─────────┘ └─────────┘ └─────────┘
pp p x
(int **) (int *) (int)
Size of Double Pointers
printf("sizeof(int) = %zu\n", sizeof(int)); // 4 bytes
printf("sizeof(int*) = %zu\n", sizeof(int*)); // 8 bytes (64-bit)
printf("sizeof(int**) = %zu\n", sizeof(int**)); // 8 bytes (64-bit)
All pointer types (regardless of levels) have the same size on a given system because they all store memory addresses.
Dereferencing Double Pointers
Levels of Dereferencing
| Expression | Result | Description |
|---|---|---|
pp | Address of pointer | The double pointer itself |
*pp | Address of variable | The pointer it points to |
**pp | The actual value | The variable's value |
Detailed Example
#include <stdio.h>
int main() {
int num = 42;
int *ptr = #
int **pp = &ptr;
// Level 0: The double pointer
printf("pp = %p (address stored in pp)\n", (void*)pp);
// Level 1: Single dereference
printf("*pp = %p (address stored in ptr)\n", (void*)*pp);
// Level 2: Double dereference
printf("**pp = %d (value of num)\n", **pp);
// Modifying through double pointer
**pp = 100;
printf("\nAfter **pp = 100:\n");
printf("num = %d\n", num); // 100
return 0;
}
Dereferencing to Modify
int a = 5, b = 10;
int *p = &a;
int **pp = &p;
// Modify value through double pointer
**pp = 50; // a is now 50
// Change which variable p points to
*pp = &b; // p now points to b
// Now **pp accesses b
printf("%d\n", **pp); // Prints 10
Triple and N-Level Pointers
Triple Pointer (Rarely Used)
int value = 5;
int *p1 = &value; // Single pointer
int **p2 = &p1; // Double pointer
int ***p3 = &p2; // Triple pointer
printf("***p3 = %d\n", ***p3); // 5
Memory Visualization
p3 (int***) → p2 (int**) → p1 (int*) → value (int)
│ │ │ │
└──────────────┴────────────┴───────────┴──► 5
When to Use Multi-Level Pointers
| Level | Common Use Case |
|---|---|
* (Single) | Accessing values by reference |
** (Double) | Modifying pointers, 2D arrays, string arrays |
*** (Triple) | 3D arrays (rare), complex data structures |
****+ | Almost never needed in practice |
Rule of Thumb
If you need more than triple pointers, reconsider your design. Use structures or simpler approaches.
Practical Applications
1. Modifying a Pointer in a Function
Without double pointer (doesn't work as intended):
void changePtr(int *p) {
int local = 100;
p = &local; // Only changes local copy of p
}
int main() {
int x = 10;
int *ptr = &x;
changePtr(ptr);
printf("%d\n", *ptr); // Still 10, not 100
return 0;
}
With double pointer (works correctly):
void changePtr(int **pp) {
static int local = 100; // Static so it persists
*pp = &local; // Modifies the original pointer
}
int main() {
int x = 10;
int *ptr = &x;
changePtr(&ptr);
printf("%d\n", *ptr); // 100
return 0;
}
2. Dynamic Memory Allocation in Functions
#include <stdio.h>
#include <stdlib.h>
void allocateArray(int **arr, int size) {
*arr = (int*)malloc(size * sizeof(int));
if (*arr == NULL) {
printf("Allocation failed!\n");
return;
}
for (int i = 0; i < size; i++) {
(*arr)[i] = i * 10;
}
}
void freeArray(int **arr) {
free(*arr);
*arr = NULL; // Prevent dangling pointer
}
int main() {
int *myArray = NULL;
int size = 5;
allocateArray(&myArray, size);
for (int i = 0; i < size; i++) {
printf("%d ", myArray[i]);
}
printf("\n");
freeArray(&myArray);
return 0;
}
3. Swapping Pointers
void swapPointers(int **p1, int **p2) {
int *temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main() {
int a = 10, b = 20;
int *pa = &a, *pb = &b;
printf("Before: *pa = %d, *pb = %d\n", *pa, *pb);
swapPointers(&pa, &pb);
printf("After: *pa = %d, *pb = %d\n", *pa, *pb);
return 0;
}
Double Pointers with Arrays
Array of Pointers
int a = 1, b = 2, c = 3;
int *arr[3] = {&a, &b, &c}; // Array of 3 pointers
int **pp = arr; // Double pointer to first element
printf("%d\n", *arr[0]); // 1
printf("%d\n", **pp); // 1
printf("%d\n", **(pp+1)); // 2
printf("%d\n", **(pp+2)); // 3
Memory Layout
arr (array of pointers):
┌─────────┬─────────┬─────────┐
│ &a │ &b │ &c │
└────┬────┴────┬────┴────┬────┘
│ │ │
▼ ▼ ▼
┌───┐ ┌───┐ ┌───┐
│ 1 │ │ 2 │ │ 3 │
└───┘ └───┘ └───┘
a b c
Array of Strings (char**)
char *names[] = {"Alice", "Bob", "Charlie"};
char **pp = names;
for (int i = 0; i < 3; i++) {
printf("%s\n", pp[i]);
// or: printf("%s\n", *(pp + i));
}
Dynamic 2D Arrays
Creating a 2D Array Dynamically
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 3, cols = 4;
// Step 1: Allocate array of row pointers
int **matrix = (int**)malloc(rows * sizeof(int*));
// Step 2: Allocate each row
for (int i = 0; i < rows; i++) {
matrix[i] = (int*)malloc(cols * sizeof(int));
}
// Step 3: Initialize values
int count = 1;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = count++;
}
}
// Step 4: Print matrix
printf("Matrix:\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
// Step 5: Free memory (reverse order)
for (int i = 0; i < rows; i++) {
free(matrix[i]);
}
free(matrix);
return 0;
}
Memory Layout of Dynamic 2D Array
matrix (int**)
│
▼
┌─────────┐
│ row0_ptr│───► ┌───┬───┬───┬───┐
├─────────┤ │ 1 │ 2 │ 3 │ 4 │
│ row1_ptr│───► ├───┼───┼───┼───┤
├─────────┤ │ 5 │ 6 │ 7 │ 8 │
│ row2_ptr│───► ├───┼───┼───┼───┤
└─────────┘ │ 9 │10 │11 │12 │
└───┴───┴───┴───┘
Alternative: Contiguous 2D Array
int rows = 3, cols = 4;
// Allocate pointers + all data contiguously
int **matrix = (int**)malloc(rows * sizeof(int*));
matrix[0] = (int*)malloc(rows * cols * sizeof(int));
// Set up row pointers
for (int i = 1; i < rows; i++) {
matrix[i] = matrix[0] + i * cols;
}
// Now matrix[i][j] works normally
// Free with:
free(matrix[0]);
free(matrix);
Command Line Arguments
Understanding main(int argc, char **argv)
The main function can receive command-line arguments:
int main(int argc, char **argv) {
// argc: argument count
// argv: argument vector (array of strings)
}
argv Memory Layout
When program runs as: ./program hello world
argv (char**)
│
▼
┌─────────┐
│ argv[0] │───► "./program\0"
├─────────┤
│ argv[1] │───► "hello\0"
├─────────┤
│ argv[2] │───► "world\0"
├─────────┤
│ NULL │ (null terminator)
└─────────┘
Processing Command Line Arguments
#include <stdio.h>
int main(int argc, char **argv) {
printf("Program name: %s\n", argv[0]);
printf("Number of arguments: %d\n", argc);
printf("\nAll arguments:\n");
for (int i = 0; i < argc; i++) {
printf(" argv[%d] = \"%s\"\n", i, argv[i]);
}
// Alternative: using pointer arithmetic
printf("\nUsing pointer arithmetic:\n");
char **ptr = argv;
while (*ptr != NULL) {
printf(" %s\n", *ptr);
ptr++;
}
return 0;
}
char *argv[] vs char **argv
Both declarations are equivalent for function parameters:
int main(int argc, char *argv[]) // Array notation
int main(int argc, char **argv) // Pointer notation
The array notation is syntactic sugar - in function parameters, arrays decay to pointers.
Common Mistakes and Pitfalls
1. Forgetting to Initialize Pointers
// WRONG
int **pp; // Uninitialized - contains garbage
**pp = 10; // CRASH! Dereferencing garbage address
// CORRECT
int value = 10;
int *ptr = &value;
int **pp = &ptr;
**pp = 20; // Safe
2. Wrong Level of Dereferencing
int x = 5;
int *p = &x;
int **pp = &p;
// WRONG - type mismatch
int *wrong = pp; // Should be int** not int*
// WRONG - too few dereferences
printf("%d\n", *pp); // Prints address, not value
// CORRECT
printf("%d\n", **pp); // Prints 5
3. Memory Leaks with Dynamic Allocation
// WRONG - memory leak
int **matrix = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
matrix[i] = malloc(cols * sizeof(int));
}
free(matrix); // Rows are leaked!
// CORRECT - free rows first
for (int i = 0; i < rows; i++) {
free(matrix[i]);
}
free(matrix);
4. Dangling Double Pointers
int** createMatrix() {
int arr[3] = {1, 2, 3}; // Local array
int *p = arr;
return &p; // WRONG! p is local, will be invalid
}
// CORRECT - use dynamic allocation
int** createMatrix() {
int **matrix = malloc(sizeof(int*));
*matrix = malloc(3 * sizeof(int));
// ...initialize...
return matrix;
}
5. Type Confusion
int x = 10;
int *p = &x;
// WRONG - &p is int**, not int*
int *wrong = &p; // Type mismatch!
// CORRECT
int **pp = &p; // Proper type
Best Practices
1. Always Initialize
// Good practice
int **pp = NULL; // Initialize to NULL
// Check before use
if (pp != NULL && *pp != NULL) {
**pp = 100;
}
2. Use typedef for Clarity
typedef int* IntPtr;
typedef int** IntPtrPtr;
IntPtr p = &value;
IntPtrPtr pp = &p;
3. Clear Naming Convention
int value;
int *ptr_value; // Prefix with ptr_
int **ptr_ptr_value; // Or ptr_ptr_
// Or suffix with _ptr
int *value_ptr;
int **value_ptr_ptr;
4. Free Memory in Reverse Order
// Allocation order: matrix -> rows
int **matrix = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
matrix[i] = malloc(cols * sizeof(int));
}
// Free in reverse: rows -> matrix
for (int i = 0; i < rows; i++) {
free(matrix[i]);
matrix[i] = NULL; // Prevent dangling
}
free(matrix);
matrix = NULL;
5. Document Pointer Ownership
/**
* Creates a dynamic 2D array.
* @param rows Number of rows
* @param cols Number of columns
* @return Pointer to 2D array. Caller is responsible for freeing.
*/
int** createMatrix(int rows, int cols);
/**
* Frees a 2D array created by createMatrix.
* @param matrix The matrix to free
* @param rows Number of rows
*/
void freeMatrix(int **matrix, int rows);
Summary
Key Concepts
| Concept | Syntax | Description |
|---|---|---|
| Declaration | int **pp; | Pointer to pointer |
| Assignment | pp = &ptr; | Store address of pointer |
| Single Deref | *pp | Access the pointer |
| Double Deref | **pp | Access the value |
| Size | sizeof(int**) | Same as any pointer |
When to Use Double Pointers
- •Modifying pointers in functions - Pass
&ptrto change whatptrpoints to - •Dynamic 2D arrays -
int **matrixfor runtime-sized matrices - •Array of strings -
char **stringsfor string collections - •Command-line arguments -
char **argvin main - •Linked list operations - Modify head pointer through function
Dereferencing Quick Reference
int x = 42;
int *p = &x;
int **pp = &p;
pp → Address of p (e.g., 0x2000)
*pp → Value in p (e.g., 0x1000 - address of x)
**pp → Value in x (42)
Common Patterns
// Pattern 1: Modify pointer in function
void modify(int **pp) { *pp = newAddress; }
modify(&myPtr);
// Pattern 2: Allocate array in function
void allocate(int **pp, int n) { *pp = malloc(n * sizeof(int)); }
allocate(&myArray, 10);
// Pattern 3: Dynamic 2D array
int **matrix = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++)
matrix[i] = malloc(cols * sizeof(int));
Practice Exercises
- •Write a function that swaps two integer pointers using a double pointer
- •Create a dynamic 2D array, fill it with values, and print it
- •Implement a function that allocates memory for an integer and returns via double pointer
- •Write a program that processes command-line arguments and prints them in reverse
- •Create an array of strings dynamically and sort them alphabetically
See the exercises.c file for hands-on practice problems with solutions.