Pointers to pointers, also known as double pointers, are a concept in C where a pointer variable holds the memory address of another pointer variable. This allows for indirect  access to a memory location  and is particularly useful in scenarios where you need to modify the value of a pointer itself or pass a pointer to a function by reference.
        
int* my_array[10]; // An array of pointers to integers
int** my_double_pointer = my_array; // A double pointer to the array of pointers
 
      The double pointer my_double_pointer stores the address of the first element of the array my_array. This means that we can use the double pointer to access any element of the array by dereferencing it twice.
        
int value = *(*my_double_pointer + 5); // Get the value of the 6th element of the array
 
        
 
#include <stdio.h>
int main() {
    int x = 10;
    int *ptr1 = &x;       // Pointer to an integer
    int **ptr2 = &ptr1;   // Pointer to a pointer to an integer
    printf("Value of x: %d\n", x);
    printf("Value pointed to by ptr1: %d\n", *ptr1);
    printf("Value pointed to by ptr2: %d\n", **ptr2);
    return 0;
}
 
 
//Output:
Value of x: 10
Value pointed to by ptr1: 10
Value pointed to by ptr2: 10
 
      In this example, ptr1 is a pointer to an integer (int *), and ptr2 is a  pointer to a pointer  to an integer (int **). ptr2 holds the address of ptr1, and  using **ptr2, we can indirectly access and modify the value of x.
       Passing Pointers to Functions
 
#include <stdio.h>
void modifyValue(int **ptr) {
    **ptr = 20;
}
int main() {
    int x = 10;
    int *ptr1 = &x;
    printf("Value of x before function call: %d\n", x);
    modifyValue(&ptr1); // Pass a pointer to pointer
    printf("Value of x after function call: %d\n", x);
    return 0;
}
 
 
//Output:
Value of x before function call: 10
Value of x after function call: 20
 
      In this example, the modifyValue function takes a pointer to a pointer as an argument. By passing the address of ptr1 to this function, we can modify the value of x indirectly through **ptr.
       Dynamic Memory Allocation
 
#include <stdio.h>
#include <stdlib.h>
int main() {
    int **matrix;
    int rows = 3;
    int cols = 4;
    // Allocate memory for a 2D array using double pointers
    matrix = (int **)malloc(rows * sizeof(int *));
    for (int i = 0; i < rows; i++) {
        matrix[i] = (int *)malloc(cols * sizeof(int));
    }
    // Assign values to the elements
    matrix[1][2] = 42;
    // Deallocate memory
    for (int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);
    return 0;
}
 
      In this example, we use double pointers to  dynamically allocate memory  for a 2D array. The matrix variable is of type int **, and we allocate memory for both rows and columns dynamically. Double pointers make it possible to create and manage dynamic multi-dimensional arrays.
       Double pointer to create a linked list
      The following code shows how to use a double pointer to create a linked list:
 
#include <stdio.h>
#include <stdlib.h>
struct node {
    int data;
    struct node* next;
};
struct node* head = NULL; // A single pointer to the head of the linked list
void add_node(int data) {
    struct node* new_node = malloc(sizeof(struct node));
    new_node->data = data;
    new_node->next = NULL;
    if (head == NULL) {
        head = new_node; // Assign the new_node directly to head
    } else {
        struct node* current = head;
        while (current->next != NULL) {
            current = current->next;
        }
        current->next = new_node; // Update the current node's next to point to new_node
    }
}
void print_list() {
    struct node* current = head;
    while (current != NULL) {
        printf("%d\n", current->data);
        current = current->next;
    }
}
int main() {
    add_node(10);
    add_node(20);
    add_node(30);
    print_list();
    return 0;
}
 
 
//Output:
10
20
30
 
       Conclusion
      Pointers to pointers are a powerful concept in C, allowing for more advanced  memory management,  dynamic data structures, and passing pointers by reference to functions. They are essential in scenarios where you need to work with pointers in a flexible and indirect manner.