Table of contents
1.
Introduction
2.
Declaration of Pointer to a Pointer in C
3.
Example of Double Pointer in C
3.1.
C
4.
How Double Pointer Works?
4.1.
C
5.
Size of Pointer to Pointer in C
5.1.
C
6.
Application of Double Pointers in C
6.1.
C
7.
Multilevel Pointers in C
7.1.
C
8.
Syntax of Triple Pointer
9.
Example 
9.1.
C
10.
Frequently Asked Questions 
10.1.
What's the main use of double pointers in C?
10.2.
Can I create more than three levels of pointers in C?
10.3.
How does memory allocation for multilevel pointers work?
11.
Conclusion
Last Updated: Apr 3, 2024
Easy

Pointer to Pointer in C

Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

In C programming, pointers stand as a fundamental concept, bridging the gap between memory addresses and actual data. A pointer to a pointer, or a double pointer, takes this concept further by holding the address of another pointer, thereby creating a multi-layered level of indirection. This might sound complex, but it's essentially a way to manage and manipulate multiple layers of data through their addresses. 

 Pointer to Pointer in C

This article will explain pointers to pointers. From their declaration, showcasing examples, explaining their inner workings, discussing their size, and exploring their applications, including the concept of multilevel pointers. 

Declaration of Pointer to a Pointer in C

When we talk about pointers, we're usually referring to a variable that stores the address of another variable. Now, a pointer to a pointer is a bit like a step further—it stores the address of a pointer that, in turn, points to another variable. It sounds a bit like a treasure map, where one clue leads to another, doesn't it? But let's keep things simple.

To declare a pointer to a pointer in C, you use two asterisks (**). Here's how it looks:

int **ptr;


In this example, ptr is a double pointer. This means ptr holds the address of another pointer that points to an integer value. It's like having a box (ptr) that contains the address of another box, which finally contains the treasure (the integer value).

To understand this better, let's see how we can set up this chain:

First, we have an integer variable:

int var = 10;


Then, we declare a normal pointer that points to our integer:

int *ptr_to_var = &var;


Finally, we declare our double pointer and make it point to the pointer from step 2:

int **ptr = &ptr_to_var;



Now, ptr points to ptr_to_var, which in turn points to var. By using ptr, we can indirectly access and manipulate the value of var.

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

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 = &num;
     
  • 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

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

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

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

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

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 DSADBMSCompetitive ProgrammingPythonJavaJavaScript, etc. Also, check out some of the Guided Paths on topics such as Data Structure andAlgorithmsCompetitive ProgrammingOperating SystemsComputer Networks, DBMSSystem Design, etc., as well as some Contests, Test Series, and Interview Experiences curated by top Industry Experts.

Live masterclass