What does ** do in C language?
Categories:
Understanding the Double Asterisk (**) in C: Pointers to Pointers
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:
- Declaration: When declaring a variable,
int *ptr;
meansptr
is a pointer that can hold the address of an integer. - Dereferencing: When used with an existing pointer variable,
*ptr
accesses the value stored at the memory addressptr
holds. For example, ifptr
points to an integerx
, then*ptr
gives you the value ofx
.
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 ofvalue
.**ptr_to_ptr
: A pointer that holds the address ofptr
.
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 ofptr
.*ptr_to_ptr
: Dereferencesptr_to_ptr
to get the value stored at its address, which is the address ofvalue
(i.e.,ptr
).**ptr_to_ptr
: Dereferences*ptr_to_ptr
(which isptr
) to get the value stored atptr
's address, which is the originalvalue
.
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.
**
is when you need to modify a pointer itself from within a function. If you pass a pointer *ptr
to a function, the function receives a copy of ptr
. To change what ptr
points to in the calling scope, you must pass its address, i.e., &ptr
, which the function receives as **
.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
***
or more) can make code difficult to read and debug. It's generally best to stick to *
and **
for most common scenarios. If you find yourself needing ***
, reconsider your design.