1

The reason for the question is that there seems to be no reason to use a double indirection like "struct **pointer".

To understand the functioning of pointers to structures as arguments of a function I have prepared an example. The aim is to see if the changes made by a function on a structure remain outside the scope of the function, or if they only cause effect inside the function. This is a common problem of passing arguments by value or by reference.

#include <stdio.h>

/* A simple structure is defined to see the effect that the functions 
 * have on it. */
typedef struct est
{
  int val1;
  int val2;
} est_t;

/* These three functions simply assign the values val1 and val2 to the 
 * structure. Each of these functions uses a different way of passing 
 * the parameter to the function referring to the structure. There are 
 * more comments below, in the definition of each function. */
void foo(int, int, est_t);
void bar(int, int, est_t*);
void baz(int, int, est_t**);

/* This is a function to print the values of a structure and make the 
 * code cleaner. It also contains a parameter about a structure! */
void print_est(est_t*);

int main (int argc, char *argv[])
{
  est_t a;

  foo(10, 11, a);
  print_est(&a);
  
  bar(20, 21, &a);
  print_est(&a);
  
  est_t *p = &a;
  baz(30, 31, &p);
  print_est(&a);
  
  return 0;
}

void foo(int v1, int v2, est_t ve)
{
  /* In this case the structure is "directly put" into the function.
   * As the structure already is inside the function, the values of each 
   * element are assigned with the ". " */
  ve.val1 = v1;
  ve.val2 = v2;
  
  /* With these printf you can see what is happening within the 
   * function. */
  printf("[foo] val1 = %d\n", ve.val1);  
  printf("[foo] val2 = %d\n", ve.val2);
}

void bar(int v1, int v2, est_t *ve)
{
  /* In this case the structure is passed into the function using a 
   * pointer. */
  ve->val1 = v1;
  ve->val2 = v2;
  
  printf("\n[bar] val1 = %d\n", ve->val1);  
  printf("[bar] val2 = %d\n", ve->val2);
}

void baz(int v1, int v2, est_t **pp)
{
  /* In this case the structure is passed into the function using a 
   * pointer to a pointer to a struct. */
  (*pp)->val1 = v1;
  (*pp)->val2 = v2;
  
  printf("\n[baz] val1 = %d\n", (*pp)->val1);  
  printf("[baz] val2 = %d\n", (*pp)->val2);
}

void print_est(est_t *addr)
{
  printf("[main] val1 = %d\n", addr->val1);
  printf("[main] val2 = %d\n", addr->val2);
}

And this is the output when you run the program.

foo(10, 11, a);  
[foo] val1 = 10  
[foo] val2 = 11  
[main] val1 = -1238356256  
[main] val2 = 32764

You can see that the values that were assigned within the function, are not kept outside

And let's see the output for bar and baz.

bar(20, 21, &a);    
[bar] val1 = 20  
[bar] val2 = 21  
[main] val1 = 20  
[main] val2 = 21

est_t *p = &a;  
baz(30, 31, &p);  
[baz] val1 = 30  
[baz] val2 = 31  
[main] val1 = 30  
[main] val2 = 31 

It seems that they do the same. I mean in both cases the values are kept outside the function.

Foo and bar are typical cases of by-value and by-reference, and even if the changes are not intended to be permanent, the idea of passing a structure by-reference can be interesting from the point of view of saving resources. See advantage of passing pointer to a struct as argument?

But as bar and baz do the same ¿is there any reason to use a double pointer to pass arguments to a function?

interesting
  • 149
  • 1
  • 10
Daniel G.
  • 25
  • 6
  • Change your functions to assign to the arguments, and you will start seeing a difference (e.g. `p = another_pointer;` in `bar`, or `*pp = another_dynamically_allocated_pointer` in `baz`). – Some programmer dude Oct 11 '20 at 12:30
  • So in your opinion it is OK to want to mutate a `struct` in the caller's context (you mutate `struct est` but it could be anything). Great. Now think about mutating an `int` in the caller's context. Is that a legitimate desire? How would you do that? What about mutating a *pointer*? How would you do that? – n. m. could be an AI Oct 11 '20 at 12:33

2 Answers2

2

is there any reason to use a double pointer to pass arguments to a function

Yes. You use a pointer whenever you want to change the argument you're passing. So if you want to change a pointer, then you need a pointer to pointer.

Simple example:

void foo(struct bar **ptr)
{
    *ptr = malloc(10 * sizeof **ptr);
    
    for(int i=0; i<10; i++)
        (**ptr)->x = i;
}
klutt
  • 30,332
  • 17
  • 55
  • 95
2

There are generally two reasons for a function to accept an argument of type T ** (for any type T):

  • the argument corresponds to an array of pointers;
  • the function is meant to update the pointer value.

As an example of the first, suppose we have code like

int main( void )
{
  char *strs[] = { "foo", "bar", "bletch", "blurga", NULL };
  func( strs );
  ...
}

strs is an array of pointers, but thanks to the usual decay rule, what func receives is a pointer to a pointer:

void func( char **s )
{
  ...
}

Alternately, your function is meant to update a pointer value. Suppose we’ve dynamically allocated an array of struct type that we need to extend with realloc and initialize the extended memory. To make that a little cleaner, we’ve put that in a separate function:

void extend( struct s **ptr, size_t *size )
{
  struct s *tmp = realloc( *ptr, *size * 2 );
  if ( tmp )
  {
    for ( size_t i = *size; i < *size * 2; i++ )
      init ( &tmp[i] );
    *ptr = tmp;
    *size *= 2;
  }
}
John Bode
  • 119,563
  • 19
  • 122
  • 198