Introduction
In C programming, pointers provide direct memory access and manipulation capabilities. While a standard pointer stores the address of a variable, a pointer to pointer—also known as a double pointer—stores the address of another pointer variable. This concept introduces an additional level of indirection, enabling more complex data structures and dynamic memory management techniques.
Understanding double pointers is essential for working with multi-dimensional arrays, modifying pointer arguments within functions, and implementing data structures such as linked lists and trees. In this article, you will gain an understanding of how double pointers function, how to declare and initialize them, and how multiple levels of indirection operate within memory.
(toc) #title=(Table of Content)
What Is a Pointer to Pointer?
A pointer to pointer is a variable that stores the memory address of another pointer variable. Whereas a single-level pointer points to a standard data variable (such as an integer or float), a double pointer points to a pointer variable. This creates a two-step chain of references.
Memory Representation of Single Pointer
Consider a standard integer variable declared and initialized:
\[ \text{int a = 10;} \]
In memory, variable a occupies four bytes (on a 32-bit system) containing the value 10. Its memory address might be 0x1000.
A pointer variable intended to store the address of a is declared as:
\[ \text{int *p = &a;} \]
Here, p stores the address 0x1000. The pointer p itself resides at a different memory location, such as 0x850.
Declaring a Double Pointer
To declare a pointer that stores the address of another pointer variable, use two asterisks:
\[ \text{int **q = &p;} \]
The declaration reads from right to left: q is a pointer to a pointer to an integer. Variable q stores the address of p (0x850), and q itself occupies its own memory location, such as 0x1046.
Types and Levels of Pointers
Pointers are categorized by their indirection level:
| Pointer Level | Declaration | Stores Address Of | Example |
|---|---|---|---|
| Zero-level (variable) | int a; |
N/A (stores value) | a = 10; |
| One-level pointer | int *p; |
Zero-level variable | p = &a; |
| Two-level pointer (double) | int **q; |
One-level pointer | q = &p; |
| Three-level pointer | int ***r; |
Two-level pointer | r = &q; |
Each additional asterisk adds one level of indirection. The data type specification (int) indicates the ultimate data type being pointed to after all dereferences.
Accessing Values Through Multiple Indirections
To retrieve the original integer value through different pointer levels, apply the dereference operator (*) repeatedly:
- Using the variable directly:
a - Using a single pointer:
*p - Using a double pointer:
**q - Using a triple pointer:
***r
Original Example
Consider a chain of pointers:
int a = 10;
int *p = &a; // p holds address of a
int **q = &p; // q holds address of p
int ***r = &q; // r holds address of q
To access the value 10 through r:
\[ ***r = **q = *p = a = 10 \]
The dereference operation proceeds stepwise: *r yields the address stored in r (which points to q). **r dereferences again to reach p. ***r dereferences a third time to reach a.
Practical Code Example
#include <stdio.h>
int main() {
int a = 10;
int *p = &a;
int **q = &p;
int ***r = &q;
// Three methods to print the same value
printf("Value of a: %d\n", a);
printf("Using single pointer *p: %d\n", *p);
printf("Using double pointer **q: %d\n", **q);
printf("Using triple pointer ***r: %d\n", ***r);
// Printing addresses
printf("Address of a: %p\n", &a);
printf("Address of p (stored in q): %p\n", q);
printf("Address of q (stored in r): %p\n", r);
// Modifying value through double pointer
**q = 25;
printf("After **q = 25, a = %d\n", a);
// Modifying value through triple pointer
***r = 78;
printf("After ***r = 78, a = %d\n", a);
return 0;
}
Valid and Invalid Assignments
Type compatibility is critical when working with multi-level pointers. The compiler enforces these rules strictly:
| Assignment | Validity | Reason |
|---|---|---|
int *p = &a; |
Valid | One-level pointer stores address of zero-level variable |
int **q = &p; |
Valid | Two-level pointer stores address of one-level pointer |
int **q = &a; |
Invalid | Two-level pointer cannot store address of zero-level variable |
int ***r = &q; |
Valid | Three-level pointer stores address of two-level pointer |
int ***r = &p; |
Invalid | Three-level pointer cannot store address of one-level pointer |
The rule follows a consistent pattern: an N-level pointer must store the address of an (N-1)-level pointer.
Common Errors to Avoid
Attempting to Assign Integer Values to Pointer Addresses
The following code produces a compilation error:
Conclusion
Pointer to pointer concepts enable sophisticated memory management patterns in C programming. By storing addresses of other pointers, developers can implement dynamic multi-dimensional arrays, modify pointer arguments within functions, and construct complex linked data structures. The key principles to remember are type compatibility across indirection levels and the relationship between the number of asterisks in declaration and dereference operations. As systems programming continues to demand efficient memory handling, proficiency with multi-level pointers remains a foundational skill for C developers.