1

I have a question about pointer.

Recently, I was looking some system programming tutorial. The teacher is talking about asprintf() Function.

According to the man page, the definition of asprintf() is:

int asprintf(char **ret, const char *format, ...);

However, I found that the teacher tends to create a single pointer and then pass the address of that single pointer to the first parameter. Like this:

int main()
{
    char *buffer;
    int r;

    r = asprintf(&buffer,"The total is %d\n",5+8);

    puts(buffer);
    printf("%d characters generated\n",r);

    return(0);
}

I am wondering why don't we declare double pointer and then pass that double pointer to the function, just like the definition.

In addition, the teacher use the same tricks in other function too, such as: getline()

Therefore, is there any advantage that we would choose single pointer over double pointer, even the definition of the function is double pointer?

Thanks for everyone for replying in advanced. :))

Peter
  • 61
  • 5
  • 2
    With "why don't we declare double pointer and then pass that double pointer to the function", what _value_ would you pass in. With `&buffer`, code passes in the address of a pointer. With `char **H; asprintf(H, ....)`, what value should `H` pass in? The goal with `asprintf(...)` is to pass in a location for the function to store some information. – chux - Reinstate Monica May 25 '21 at 19:35
  • It is a pointer which points an another pointer. char is a variable which can hold one byte. The reason why *char is used is to hold multiple characters representing a string. So if you would like to pass the string’s address to a function it will become a pointer to pointer. – sigkilled May 25 '21 at 19:40
  • You could also use store the address of buffer in a local variable and pass that, along the lines of `char **pbuffer = &buffer; asprintf(pbuffer, "The total is");`, but I prefer passing in `&buffer`. – 1201ProgramAlarm May 25 '21 at 19:43

3 Answers3

1

asprintf will allocate some memory and write the printf result into that memory.

To get this resulting memory buffer, you pass a pointer to a local variable (in your case: buffer) as the first argument. This pointer is then dereferenced in the function, to change the value of your local variable, so the code in asprintf semantically does something like this:

int asprintf(char **ret, const char *format, ...) {
    *ref = malloc(some_size);
    sprintf(*ret, format, ...);
}

What would now happen if you didn't have a local variable of type char * and passed a pointer to this variable, but instead had a local variable of type char **, like you suggested:

char **buffer;
asprintf(buffer, "The total is %d\n", 5+8);

Well, asprintf would again just dereference this pointer you just passed. But in this code, it doesn't yet point to anything meaningful, so this would result in an error. We must have enough memory allocated at the address we are passing to asprintf so it can store a char* there for us.

Now, this would work:

char *buffer;
char **pointer_to_buffer = &buffer;
asprintf(pointer_to_buffer, "The total is %d\n", 5+8);

but it is just more complicated and verbose.

But why do we do this whole pointer-to-pointer thing? Well, you would certainly first try to simply pass the pointer, instead of a pointer-to-pointer, like this:

char *buffer;
asprintf(buffer, "The total is %d\n", 5+8);

with asprintf semantically doing something like this:

int asprintf(char *ret, const char *format, ...) {
    ref = malloc(some_size);
    sprintf(ret, format, ...);
}

However, it is critical that here, buffer is passed by value. This means that when asprintf locally modifies ret, it modifies a copy of the value of our buffer. We, the caller, will not be able to see this change in our variable buffer. This is why we pass a pointer-to our local variable buffer, not the value itself.

He3lixxx
  • 3,263
  • 1
  • 12
  • 31
1

I am wondering why don't we declare double pointer and then pass that double pointer to the function, just like the definition.

Because you want to update the value stored in buffer. The type of the expression &buffer is char **.

Remember, in order for a function to write to any of its parameters, you must pass a pointer to that parameter:

void foo( T *ptr )
{
  *ptr = new_T_value(); // writes a new value of type T to the thing ptr points to
}

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

Thus, the following are true:

 ptr == &var // T * == T *
*ptr ==  var // T   == T

IOW, writing to the expression *ptr is the same as writing to var.

So, why not just create a pointer variable in bar and pass it to foo? If we wrote something like

void bar( void )
{
  T *vptr;
  foo( vptr );
}

the problem is that vptr doesn't point to anything meaningful - we have

 ptr ==  vptr         // T * == T *
*ptr == *vptr == ???  // T   == T   == ???

There's no object of type T for us to update. Now, we could create another object for vptr to point to:

void bar( void )
{
  T var;
  T *vptr = &var;
  foo( vptr );
}

and that will work as expected, but the extra pointer variable is redundant in this case.

The same logic holds for pointer objects - let's replace T with a pointer type, P *. Then our code becomes:

void foo( P * *ptr ) // or P **ptr
{
  *ptr = new_P_star_value(); // writes a new value of type P * to the thing ptr points to
}

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

which gives us

  ptr ==  &var
 *ptr ==   var ==  some_P_star_value
**ptr ==  *var == *some_P_star_value == some_P_value

Same thing as above - we want to write a new value to var. To do that we pass a pointer to it, even though var is already a pointer type. This is why multiple indirection exists in the first place.

Most of the time when you see a function that has a pointer or pointer-to-pointer parameter, it's expecting you to pass the address of another object (obtained with the & operator), not a pointer variable as such.

John Bode
  • 119,563
  • 19
  • 122
  • 198
0

If you want to change an object used as an argument in a function you need to pass it by reference.

In C passing by reference means passing an object indirectly through a pointer to it. Thus dereferencing the pointer a function can get a direct access to the original pointer.

Consider this demonstrative program and compare the result of calls of the two functions f and g.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


void f( char *s )
{
    s = malloc( 14 );
    strcpy( s, "Hello World!" );    
}

void g( char **ps )
{
    *ps = malloc( 14 );
    strcpy( *ps, "Hello World!" );  
}

int main(void) 
{
    char *s = NULL;
    
    f( s );
    
    if ( s != NULL ) puts( s );
    else puts( "s is a null pointer." );

    g( &s );
    
    if ( s != NULL ) puts( s );
    else puts( "s is a null pointer." );

    free( s );
    
    return 0;
}

The program output is

s is a null pointer.
Hello World!

The function f accepts the pointer s declared in main by value. It means that the function deals with a copy of the value of the pointer s. Changing the copy does not affect the original pointer s. So the function produces a memory leak because it allocates dynamically a memory and the address of the allocated memory stored in the local variable (function parameter) s will be lost after exiting the function.

The function g accepts the pointer s by reference through a pointer to it. So dereferencing its parameter the function has a direct access to the original pointer s and can change it.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335