Example of Double Pointer in C
Imagine you have a variable, and you want to manage it through a pointer. But instead of just one layer, we're adding another layer by using a double pointer.
Here's a simple example to illustrate this:
C
#include <stdio.h>
int main() {
int val = 100; // Our original variable
int *ptr = &val; // A pointer pointing to 'val'
int **dptr = &ptr; // A double pointer pointing to 'ptr'
printf("Value of val = %d\n", val); // Direct access
printf("Value via ptr = %d\n", *ptr); // Indirect access
printf("Value via dptr = %d\n", **dptr); // Double indirect access
return 0;
}

You can also try this code with Online C Compiler
Run Code
Output
Value of val = 100
Value via ptr = 100
Value via dptr = 100
In this code:
-
val is a normal integer variable.
-
ptr is a pointer that holds the address of val.
-
dptr is a double pointer that holds the address of ptr.
When we print the values:
-
val gives us the value directly.
-
*ptr uses the single pointer to give us the value indirectly.
-
**dptr goes through two levels of indirection to get the value, first accessing ptr and then val.
This example shows how double pointers can be used to access values indirectly through multiple levels. It's a straightforward concept that can be very powerful in more complex scenarios like dynamic memory allocation or when working with arrays of pointers.
How Double Pointer Works?
A double pointer is essentially a pointer that points to another pointer, creating a chain of references to the final data.
When you declare a double pointer, like int **dptr;, think of it as creating a reference to a reference. The first level, marked by the first asterisk (*), points to a pointer. The second level, indicated by the second asterisk, points to the actual data.
To visualize this, imagine a scenario where we have:
-
An integer value, say int num = 30;
-
A pointer that points to this integer, int *ptr = #
-
A double pointer that points to the pointer, int **dptr = &ptr;
The double pointer dptr doesn't directly hold the value of num. Instead, it points to ptr, which then points to num. Accessing the value stored in num through dptr involves two steps: dereferencing dptr to get to ptr and then dereferencing ptr to reach num.
In code, accessing the value of num via dptr looks like this: **dptr. The first dereference (*dptr) gets us to ptr, and the second (**dptr) gets us to the value of num.
This mechanism allows for dynamic data structures like linked lists or trees, where nodes can contain pointers to other nodes, facilitating complex data management and manipulation.
Another example to understand how Double Pointer works
In this example, we'll create a simple program that uses a double pointer to modify the value of an integer variable.
C
#include <stdio.h>
int main() {
int value = 10; // Our original integer variable
int *ptr = &value; // Pointer to 'value'
int **dptr = &ptr; // Double pointer pointing to 'ptr'
// Printing the original value of 'value'
printf("Original value of value: %d\n", value);
// Modifying 'value' through the double pointer 'dptr'
**dptr = 20;
// Printing the modified value of 'value'
printf("Modified value of value: %d\n", value);
return 0;
}

You can also try this code with Online C Compiler
Run Code
Output
Original value of value: 10
Modified value of value: 20
In this example:
-
We start with an integer variable value set to 10.
-
We then create a pointer ptr that points to value.
-
Next, we declare a double pointer dptr that points to ptr.
-
To change the value of value using dptr, we use **dptr = 20;. This line of code does two things:
-
The first dereference (*dptr) accesses the pointer ptr.
-
The second dereference (**dptr) accesses the value that ptr points to, which is value, and then updates it to 20.
- After executing this code, you'll see that the value of value has been successfully changed from 10 to 20 using the double pointer dptr.
Size of Pointer to Pointer in C
When it comes to pointers in C, a common question is about their size. How much space does a pointer actually take up in memory?
Well, the size of a pointer, whether it's a regular pointer or a double pointer, typically depends on the architecture of the system it's running on. It's not about the type of data it points to, but the system itself.
For most systems, especially those that are 32-bit, a pointer size is generally 4 bytes. On 64-bit systems, pointers are usually 8 bytes. This size remains consistent whether the pointer is pointing to a char, an int, or even another pointer, like a double pointer.
So, whether you have int *ptr or int **dptr, the size in memory for these pointers would be the same on a specific system, because they're both just addresses.
Let's look at a simple code example to see this in action:
C
#include <stdio.h>
int main() {
int **dptr;
// Printing the size of the double pointer
printf("Size of double pointer: %lu bytes\n", sizeof(dptr));
return 0;
}

You can also try this code with Online C Compiler
Run Code
Output
Size of double pointer: 8 bytes
In this code, we declare a double pointer dptr and then use sizeof(dptr) to print out its size in bytes. When you run this code on your system, it will show you the size of a double pointer, which should align with the typical sizes for your system's architecture (4 bytes for 32-bit, 8 bytes for 64-bit).
Understanding the size of pointers is important for memory management, especially when you're dealing with dynamic memory allocation, where knowing the exact amount of memory to allocate is crucial.
Application of Double Pointers in C
One common use of Double Pointer is in dynamic memory allocation, where they enable the creation and manipulation of dynamic data structures like linked lists, trees, and graphs.
For example, in a linked list, each node contains a pointer to the next node. If you want to modify the head of the list within a function, you would need to pass a pointer to the pointer of the head node. This way, any changes made to the head pointer inside the function are reflected outside the function as well.
Another application is in functions that need to return more than one value. By passing the addresses of variables via double pointers, a function can directly modify those variables, effectively "returning" multiple values.
Here's a simple example to demonstrate the use of double pointers in dynamic memory allocation:
C
#include <stdio.h>
#include <stdlib.h>
void allocateMemory(int **ptr, int size) {
*ptr = (int *)malloc(size * sizeof(int));
if (*ptr != NULL) {
for(int i = 0; i < size; i++) {
*(*ptr + i) = i + 1; // Assigning values
}
}
}
int main() {
int *arr = NULL;
int size = 5;
// Allocating memory and assigning values using double pointer
allocateMemory(&arr, size);
// Printing the allocated and assigned values
for(int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
// Freeing the allocated memory
free(arr);
return 0;
}

You can also try this code with Online C Compiler
Run Code
Output
1 2 3 4 5
In this example, the allocateMemory function uses a double pointer to allocate memory for an array and fill it with values. By passing the address of arr (&arr), any modifications made to arr within allocateMemory are reflected in main. This demonstrates the power of double pointers in working with dynamic memory and modifying data outside the current function scope.
Double pointers are a powerful tool in C, especially when dealing with dynamic data structures and when you need to manipulate memory and variables directly through functions.
Multilevel Pointers in C
In C programming, multilevel pointers are pointers that point to other pointers, creating multiple levels of indirection. While a double pointer is a common example, with two levels of indirection, you can have pointers with more levels, like triple pointers (int ***triplePtr) or even more. These are used when you need to manage data in a highly dynamic and complex manner, such as in multidimensional arrays or complex data structures like trees with nodes containing pointers to other nodes.
A triple pointer, for example, might be used to point to a two-dimensional array (an array of pointers, where each pointer points to an array), allowing for dynamic resizing of both rows and columns. This can be particularly useful in situations where the size of the data structure isn't known ahead of time and needs to be determined at runtime.
Here's a simple example to illustrate the use of a triple pointer:
C
#include <stdio.h>
#include <stdlib.h>
int main() {
int ***triplePtr; // Declaring a triple pointer
int rows = 2, cols = 3, depth = 2;
// Allocating memory for the triple pointer
triplePtr = (int ***)malloc(depth * sizeof(int **));
for(int i = 0; i < depth; i++) {
triplePtr[i] = (int **)malloc(rows * sizeof(int *));
for(int j = 0; j < rows; j++) {
triplePtr[i][j] = (int *)malloc(cols * sizeof(int));
for(int k = 0; k < cols; k++) {
triplePtr[i][j][k] = i + j + k; // Assigning values
}
}
}
// Printing values
for(int i = 0; i < depth; i++) {
for(int j = 0; j < rows; j++) {
for(int k = 0; k < cols; k++) {
printf("triplePtr[%d][%d][%d] = %d\n", i, j, k, triplePtr[i][j][k]);
}
}
}
// Freeing allocated memory
for(int i = 0; i < depth; i++) {
for(int j = 0; j < rows; j++) {
free(triplePtr[i][j]);
}
free(triplePtr[i]);
}
free(triplePtr);
return 0;
}

You can also try this code with Online C Compiler
Run Code
Output
triplePtr[0][0][0] = 0
triplePtr[0][0][1] = 1
triplePtr[0][0][2] = 2
triplePtr[0][1][0] = 1
triplePtr[0][1][1] = 2
triplePtr[0][1][2] = 3
triplePtr[1][0][0] = 1
triplePtr[1][0][1] = 2
triplePtr[1][0][2] = 3
triplePtr[1][1][0] = 2
triplePtr[1][1][1] = 3
triplePtr[1][1][2] = 4
In this example, triplePtr is used to create a 3D array, with depth, rows, and cols. Memory is dynamically allocated for each dimension, and values are assigned in a nested manner. This demonstrates how multilevel pointers can manage complex, dynamically sized structures, offering a high degree of flexibility in C programming.
Note -: Multilevel pointers, while powerful, also increase complexity and the potential for errors, so they should be used with care, ensuring proper memory allocation and deallocation to avoid leaks.
Syntax of Triple Pointer
A triple pointer is essentially a pointer to a double pointer, adding an extra level of indirection. This can be particularly useful for creating dynamic multidimensional arrays or managing a complex hierarchy of pointers for advanced data structures.
To declare a triple pointer, you use three asterisks (***) in front of the pointer name. Here's how it looks:
int ***triplePtr;
In this declaration, triplePtr is a triple pointer. It can point to a double pointer, which in turn points to a single pointer, leading finally to the actual data.
Let's break down an example to see how you can work with a triple pointer:
#include <stdio.h>
#include <stdlib.h>
int main() {
// Declaring a triple pointer
int ***triplePtr;
// Allocating memory for the triple pointer
triplePtr = (int ***)malloc(2 * sizeof(int **)); // For 2 double pointers
for (int i = 0; i < 2; i++) {
triplePtr[i] = (int **)malloc(3 * sizeof(int *)); // Each double pointer to 3 single pointers
for (int j = 0; j < 3; j++) {
triplePtr[i][j] = (int *)malloc(4 * sizeof(int)); // Each single pointer to an array of 4 ints
// Assigning values to the array elements
for (int k = 0; k < 4; k++) {
triplePtr[i][j][k] = i * j * k; // Example value assignment
}
}
}
// Use triplePtr for some operations, like printing values
// ...
// Free the allocated memory
// ...
return 0;
}
In this code, triplePtr is used to create a 3-dimensional array dynamically. First, memory is allocated for 2 double pointers. Then, each of those double pointers is allocated memory for 3 single pointers. Finally, each single pointer is allocated memory for an array of 4 integers. Values are assigned in a nested loop, demonstrating how a triple pointer can manage a 3-dimensional structure.
Note -: Working with triple pointers involves careful memory management, including allocating and freeing memory appropriately to avoid leaks. It's a powerful tool, but with great power comes the need for careful handling to keep your programs efficient and error-free.
Example
This example will create a 3-dimensional dynamic array using a triple pointer, fill it with values, and then print those values. Finally, it will free the allocated memory to avoid leaks.
C
#include <stdio.h>
#include <stdlib.h>
int main() {
int depth = 2, rows = 3, cols = 4; // Dimensions of the 3D array
int ***triplePtr;
// Allocating memory for depth, which points to rows
triplePtr = (int ***)malloc(depth * sizeof(int **));
for (int i = 0; i < depth; i++) {
// For each depth, allocate memory for rows, which point to cols
triplePtr[i] = (int **)malloc(rows * sizeof(int *));
for (int j = 0; j < rows; j++) {
// For each row, allocate memory for cols
triplePtr[i][j] = (int *)malloc(cols * sizeof(int));
// Fill the 3D array with values
for (int k = 0; k < cols; k++) {
triplePtr[i][j][k] = i + j + k; // Assigning a simple value for demonstration
}
}
}
// Printing the values in the 3D array
for (int i = 0; i < depth; i++) {
for (int j = 0; j < rows; j++) {
for (int k = 0; k < cols; k++) {
printf("triplePtr[%d][%d][%d] = %d\n", i, j, k, triplePtr[i][j][k]);
}
}
}
// Freeing the allocated memory to avoid memory leaks
for (int i = 0; i < depth; i++) {
for (int j = 0; j < rows; j++) {
free(triplePtr[i][j]); // Free each cols array
}
free(triplePtr[i]); // Free each rows array
}
free(triplePtr); // Finally, free the top-level depth array
return 0;
}

You can also try this code with Online C Compiler
Run Code
Output
triplePtr[0][0][0] = 0
triplePtr[0][0][1] = 1
triplePtr[0][0][2] = 2
triplePtr[0][0][3] = 3
triplePtr[0][1][0] = 1
triplePtr[0][1][1] = 2
triplePtr[0][1][2] = 3
triplePtr[0][1][3] = 4
triplePtr[0][2][0] = 2
triplePtr[0][2][1] = 3
triplePtr[0][2][2] = 4
triplePtr[0][2][3] = 5
triplePtr[1][0][0] = 1
triplePtr[1][0][1] = 2
triplePtr[1][0][2] = 3
triplePtr[1][0][3] = 4
triplePtr[1][1][0] = 2
triplePtr[1][1][1] = 3
triplePtr[1][1][2] = 4
triplePtr[1][1][3] = 5
triplePtr[1][2][0] = 3
triplePtr[1][2][1] = 4
triplePtr[1][2][2] = 5
triplePtr[1][2][3] = 6
In this code:
-
We start by declaring our triple pointer triplePtr and setting the dimensions for our 3D array (depth, rows, cols).
-
We allocate memory for each level of the array, starting from the top level (depth) and moving down to rows and then cols.
-
We then fill this dynamically allocated 3D array with values, in this case, a simple sum of its indices for demonstration.
- After printing all the values stored in the 3D array, we carefully free the allocated memory, starting from the innermost allocations (cols) and moving outward to rows and finally depth.
Frequently Asked Questions
What's the main use of double pointers in C?
Double pointers are commonly used for dynamic memory allocation, managing dynamic data structures like linked lists, and for functions that need to modify the actual content of a pointer passed as an argument.
Can I create more than three levels of pointers in C?
Yes, you can create multiple levels of pointers (e.g., quadruple pointers, quintuple pointers), but with each additional level, the complexity and risk of errors increase. It's generally best to keep the design as simple as possible.
How does memory allocation for multilevel pointers work?
Memory allocation for multilevel pointers involves allocating memory for each level of the pointer. For a triple pointer, you first allocate memory for the double pointers it points to, then for the single pointers each double pointer points to, and finally for the actual data each single pointer points to.
Conclusion
In this article, we learned about the most important concept of C programming that is pointers, focusing on double and multilevel pointers. Starting with the basics, we explored how to declare a pointer to a pointer, looked into examples to understand them better, and understood the working of these powerful constructs. We discussed the consistent size of pointers across various types, which is essential for efficient memory management. Apart from this we also learned how multilevel and triple pointer works.
You can refer to our guided paths on the Coding Ninjas. You can check our course to learn more about DSA, DBMS, Competitive Programming, Python, Java, JavaScript, etc. Also, check out some of the Guided Paths on topics such as Data Structure andAlgorithms, Competitive Programming, Operating Systems, Computer Networks, DBMS, System Design, etc., as well as some Contests, Test Series, and Interview Experiences curated by top Industry Experts.