What does ** do in C language?

Learn what does ** do in c language? with practical examples, diagrams, and best practices. Covers arrays, c, pointers development techniques with visual explanations.

Understanding the Double Asterisk (**) in C: Pointers to Pointers

A conceptual diagram illustrating a pointer pointing to another pointer, which then points to a data value in memory. Three boxes are linked: 'Pointer to Pointer' (**) -> 'Pointer' (*) -> 'Value'. Use blue for pointers, green for values, and arrows for connections.

Explore the meaning and practical applications of the double asterisk (**) in C programming, a powerful construct for managing memory and passing pointers by reference.

In C programming, the asterisk * is fundamental for working with pointers. It's used to declare a pointer variable and to dereference a pointer (access the value it points to). However, you'll often encounter **, the double asterisk. This seemingly small addition signifies a 'pointer to a pointer' – a variable that stores the memory address of another pointer. Understanding ** is crucial for advanced memory management, dynamic memory allocation, and effectively modifying pointers within functions.

The Basics: Pointers and Dereferencing

Before diving into **, let's quickly recap *. A single asterisk * is used in two primary contexts:

  1. Declaration: When declaring a variable, int *ptr; means ptr is a pointer that can hold the address of an integer.
  2. Dereferencing: When used with an existing pointer variable, *ptr accesses the value stored at the memory address ptr holds. For example, if ptr points to an integer x, then *ptr gives you the value of x.
int main() {
    int value = 10;
    int *ptr_to_value = &value; // ptr_to_value stores the address of 'value'

    printf("Value: %d\n", value);           // Output: Value: 10
    printf("Address of value: %p\n", &value);
    printf("ptr_to_value: %p\n", ptr_to_value); // Output: Same as address of value
    printf("Value via ptr_to_value: %d\n", *ptr_to_value); // Output: Value via ptr_to_value: 10

    *ptr_to_value = 20; // Change value through the pointer
    printf("New value: %d\n", value);       // Output: New value: 20

    return 0;
}

Basic pointer declaration and dereferencing

What is a Pointer to a Pointer (**)?

A pointer to a pointer, declared with **, is a variable that stores the memory address of another pointer. It creates an additional level of indirection. If ptr is a pointer to an int, then ptr_to_ptr (declared as int **ptr_to_ptr;) would store the address of ptr.

Think of it like this:

  • value: The actual data (e.g., 10).
  • *ptr: A pointer that holds the address of value.
  • **ptr_to_ptr: A pointer that holds the address of ptr.

A memory diagram showing three boxes: 'value' (containing 10) at address 0x100, 'ptr_to_value' (containing 0x100) at address 0x200, and 'ptr_to_ptr' (containing 0x200) at address 0x300. Arrows connect 'ptr_to_value' to 'value' and 'ptr_to_ptr' to 'ptr_to_value'. Each box has its address and content clearly labeled.

Visualizing a pointer to a pointer in memory

To access the original value using a pointer to a pointer, you need to dereference twice:

  • ptr_to_ptr: Gives you the address of ptr.
  • *ptr_to_ptr: Dereferences ptr_to_ptr to get the value stored at its address, which is the address of value (i.e., ptr).
  • **ptr_to_ptr: Dereferences *ptr_to_ptr (which is ptr) to get the value stored at ptr's address, which is the original value.
int main() {
    int value = 100;
    int *ptr_to_value = &value;
    int **ptr_to_ptr = &ptr_to_value; // ptr_to_ptr stores the address of ptr_to_value

    printf("Value: %d\n", value); // 100

    printf("\nAddress of value: %p\n", &value);
    printf("ptr_to_value (content): %p\n", ptr_to_value); // Same as &value
    printf("ptr_to_value (address): %p\n", &ptr_to_value);

    printf("\nptr_to_ptr (content): %p\n", ptr_to_ptr); // Same as &ptr_to_value

    // Accessing value through ptr_to_ptr
    printf("\n*ptr_to_ptr (address of value): %p\n", *ptr_to_ptr); // Same as ptr_to_value content
    printf("**ptr_to_ptr (value): %d\n", **ptr_to_ptr); // 100

    // Modifying value through ptr_to_ptr
    **ptr_to_ptr = 200;
    printf("New value via **ptr_to_ptr: %d\n", value); // 200

    return 0;
}

Demonstrating pointer to pointer declaration and dereferencing

Practical Applications of **

Pointers to pointers are not just theoretical constructs; they are essential for several common C programming patterns.

1. Modifying Pointers in Functions (Pass by Reference)

When you pass a pointer to a function, it's passed by value. This means the function receives a copy of the pointer's address. If you want the function to change the original pointer (e.g., make it point to a newly allocated memory block or NULL), you must pass the address of the pointer itself. This is where ** comes in.

#include <stdio.h>
#include <stdlib.h>

// Function to allocate memory and make 'ptr' point to it
void allocateMemory(int **ptr, int size) {
    *ptr = (int *)malloc(size * sizeof(int));
    if (*ptr == NULL) {
        perror("Memory allocation failed");
        exit(EXIT_FAILURE);
    }
    printf("Inside function: Allocated memory at %p\n", *ptr);
}

int main() {
    int *myArray = NULL; // Initialize pointer to NULL
    printf("Before allocation: myArray = %p\n", myArray);

    allocateMemory(&myArray, 5); // Pass the address of myArray

    printf("After allocation: myArray = %p\n", myArray);

    // Use the allocated memory
    for (int i = 0; i < 5; i++) {
        myArray[i] = i * 10;
        printf("myArray[%d] = %d\n", i, myArray[i]);
    }

    free(myArray); // Don't forget to free the memory
    myArray = NULL; // Set to NULL after freeing

    return 0;
}

Using ** to allocate memory and update a pointer from a function

2. Working with Arrays of Pointers

An array of pointers is essentially a list where each element is a pointer. If you want to pass such an array to a function and modify the pointers within that array, you might use **. For example, char **argv in the main function signature is a classic example: it's an array of pointers to character arrays (strings).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Function to print an array of strings
void printStrings(char **strings, int count) {
    for (int i = 0; i < count; i++) {
        printf("String %d: %s\n", i, strings[i]);
    }
}

int main() {
    char *names[] = {"Alice", "Bob", "Charlie", NULL}; // Array of char pointers
    int count = 0;
    for (int i = 0; names[i] != NULL; i++) {
        count++;
    }

    printf("Original strings:\n");
    printStrings(names, count);

    // Example of how argv works: char **argv is an array of char pointers
    // Each char * points to the beginning of a string (an array of chars)

    return 0;
}

Example of an array of pointers (similar to char **argv)

3. Implementing Linked Lists and Data Structures

When building dynamic data structures like linked lists, you often need to modify the head pointer of the list. If a function is responsible for adding the first node or deleting the last node, it needs to update the head pointer in the calling scope. Passing &head (which is Node **) allows the function to do this.

#include <stdio.h>
#include <stdlib.h>

// Define a simple linked list node
typedef struct Node {
    int data;
    struct Node *next;
} Node;

// Function to add a new node to the beginning of the list
// Takes a pointer to the head pointer (Node **)
void push(Node **head_ref, int new_data) {
    Node *new_node = (Node *)malloc(sizeof(Node));
    if (new_node == NULL) {
        perror("Memory allocation failed");
        exit(EXIT_FAILURE);
    }
    new_node->data = new_data;
    new_node->next = (*head_ref); // Make new node point to the current head
    (*head_ref) = new_node;       // Update the head pointer in the caller's scope
}

// Function to print the linked list
void printList(Node *node) {
    while (node != NULL) {
        printf(" %d", node->data);
        node = node->next;
    }
    printf("\n");
}

int main() {
    Node *head = NULL; // Start with an empty list

    printf("Initial list: ");
    printList(head);

    push(&head, 10); // Pass address of head
    push(&head, 20);
    push(&head, 30);

    printf("List after pushes: ");
    printList(head);

    // Free memory (simplified for example)
    Node *current = head;
    while (current != NULL) {
        Node *next = current->next;
        free(current);
        current = next;
    }
    head = NULL;

    return 0;
}

Using ** to modify the head of a linked list