3

I am familiar with basic C pointers. Just wanted to ask what is the actual use of double pointers or for that matter n pointer?

#include<stdio.h>
int main()
{
    int n = 10 , *ptr , **ptr_ptr ;
    ptr = &n;
    ptr_ptr = &ptr;
    printf("%d", **ptr_ptr);
    return(0);
}

**ptr_ptr is printing 10 but how?

4
  • 1
    Its a pointer to a pointer.
    – user40980
    Commented Jun 26, 2014 at 14:56
  • need more elaboration. Commented Jun 26, 2014 at 15:04
  • Its a pointer that points to a pointer, that points to the address of the integer n. You dereference it once you get the pointer it was pointing to, dereference it twice you get the object pointed to by the pointer ptr_ptr is pointing to. Commented Jun 26, 2014 at 15:22
  • I really expected this to be a duplicate, but surprisingly it's not. Definitely worth an answer.
    – david.pfx
    Commented Jun 28, 2014 at 7:01

4 Answers 4

7

ptr_ptr is a pointer to a pointer to an int, and it points at ptr. ptr is a pointer to an int, and it points at n. n is an int, which has been set to 10.

A prefix '*' is the de-reference operator, meaning "the thing pointed to by". So **ptr_ptr evaluates to *ptr which evaluates to n, which is 10. If it helps, consider **ptr_ptr as equivalent to *(*ptr_ptr).

What's it for? Two possible uses are

  1. Passing a pointer as a parameter to a function, where you want the function to be able to change the pointer to a different one.
  2. Passing an array of pointers to a function, as in the classic int main(int argc, char **argv).

I have never encountered any need for an int ***ptr_ptr_ptr, but it's valid C.

2
  • 2
    Only Three Star Programmers use ***foo.
    – user40980
    Commented Jun 26, 2014 at 15:36
  • Confession time: within the past decade I have worked on C++ classes that used types like double*** -- but I swear they were already that way when I found them. Someone then extended one of those classes to one that uses types like double****. The idea of these types had to do with multidimensional arrays; I'm not saying it was a good idea.
    – David K
    Commented Jun 26, 2014 at 20:14
3

A pointer-to-pointer simply means that you have an address of a memory location where some other pointer is stored. You dereference it twice to get to the final typed value.

int i = 123;
int* pi = &i;
int ** ppi = &pi;
int j = **ppi;

There are a surprising number of situations in which you need to use one of these beasties.

  1. As an output argument of a function call, pass a pointer to the pointer you want output or modified.
  2. Accessing an array of C null-terminated strings; each string is itself a pointer. The argc argument to main looks like that.
  3. 'Jagged' or N-dimensional arrays, stored as array of pointer to data. The data will usually be allocated with malloc().
  4. A 'handle' for a memory block, so the owner of the block can move it without the user of the block needing to know.
  5. Manipulating a structure that itself contains pointers, such as linked lists, trees and so on. It's basically impossible to insert an item into a linked list without them.

It's important to keep these conceptually separate. Although they all seem to use the same construct, in reality the underlying purpose is quite different. And I'm sure there are others I've missed.

I won't provide more code. There is an excellent example with diagrams and code here: http://www.eskimo.com/~scs/cclass/int/sx8.html.

2

Multiple indirection generally occurs in the following scenarios: writing to a parameter of pointer type, and building an N-dimensional array by pieces.

Writing to a parameter of pointer type

For any function parameter of type T, if you want the function to modify the value of the parameter and have that new value reflected in the caller, you must pass a pointer:

void foo( T *ptr )
{
  *ptr = new_value(); // writes new value to thing pointed to by ptr
}

void bar( void )
{
  T var;
  foo( &var ); // writes new value to var
}

The expression *ptr in foo refers to the same object in memory as var in bar, so writing to *ptr is equivalent to writing to var.

If T is a pointer type Q *, then you wind up with a pointer to a pointer:

void foo( Q **ptr )
{
  *ptr = new_value(); // writes new value to thing pointed to by ptr
}

void bar( void )
{
  Q *var;
  foo( &var ); // writes new value to var
}

In this second case, you want to modify the pointer value stored in var. Again, both *ptr and var refer to the same memory location, but this time, var contains a pointer value. You could replace Q with R *, giving

void foo( R ***ptr )
{
  *ptr = new_value(); // writes new value to thing pointed to by ptr
}

void bar( void )
{
  R **var;
  foo( &var ); // writes new value to var
}

The semantics are the same in all three cases; we write a new value to var through the expression *ptr; it's just the type of var is a pointer type, meaning the type of ptr must be a pointer type with one more level of indirection.

Building an N-dimensional array by pieces

Suppose you want to allocate an NxM array of T, but you don't know the values of N or M at compile time. One approach is to declare a pointer to a pointer, then allocate an array of pointers to it:

T **arr; // arr will point to an NxM array of T
size_t n, m;
/**
 * get values for n and m
 */
arr = malloc( sizeof *arr * n ); // allocate N objects of type T *
if ( arr )
{
  for ( size_t i = 0; i < n; i++ )
  {
    arr[i] = malloc( sizeof *arr[i] * m ); // allocate M objects of type T
    if ( arr[i] )
    {
      for ( size_t j = 0; i < m; i++ )
      {
        arr[i][j] = some_value();
      }
    }
  }
}

The same general mechanism can be applied for higher-dimensioned arrays:

T ***arr3;
...
arr3 = malloc( sizeof *arr3 * n );
...
  arr3[i] = malloc( sizeof *arr3[i] * m );
  ...
    arr3[i][j] = malloc( sizeof *arr3[i][j] * k );

There are a few other scenarios where you see multiple indirection, but those are the two main ones.

It's rare in practice to see more than three levels of indirection, though.

1

One of the advantages of being old and grey is we remember things that happened before you youngsters were born.

The original Apple Macintosh (born in 1984) was VERY short of RAM. It did dynamic memory allocation, and it had to provide for easy coalescing of free blocks, to make bigger free blocks, to avoid the problems of fragmentation. To support this, the operating system allocated a block of memory JUST for pointers to allocated blocks, and then the memory allocator, instead of returning a pointer to the newly-allocated block, put the pointer in the pointer block and returned a "handle", a pointer to that pointer. This allowed the operating system to move those allocated blocks around, without warning, and update the pointer in the pointer block. The user's handle, which pointed into the pointer block, never changed.

This allowed an allocated block sitting between two free blocks to be moved and the two free blocks to be combined into one big free block.

Now, you had to be aware that this was going on. You had to know not to cache the second pointer somewhere. The original Mac used cooperative multitasking, so you could assume that blocks were not moving around while you were working on them, but you had to assume that, when you got the processor back after yielding it, NONE of the blocks you knew about would be where they were. However, your handles never changed, so that was OK. As long as you used handles everywhere, you were safe.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.