Pointer to Pointer Paramters (aka void **)

B

bwaichu

In some APIs, we see char ** or void ** as a parameter. I never
distinguished between declaring a variable as char **x and passing x as
the parameter from declaring a variable char *x and passing &x. I also
consider the two the same. And since I debug everything I write, I
also saw know difference while debugging. I have also used them pass
data in and out of functions as well.

Now, I was pointed out today that there is a difference. I was even
pointed out this is addressed in the standards.

What I would like to know is what is the proper way to handle APIs that
have functions with void ** parameters.

One interesting item brought up is that I might end up overwriting a
previously declared variable.
Anyway, I hope to learn more.

Brian
 
J

J. J. Farrell

In some APIs, we see char ** or void ** as a parameter. I never
distinguished between declaring a variable as char **x and passing x as
the parameter from declaring a variable char *x and passing &x. I also
consider the two the same. And since I debug everything I write, I
also saw know difference while debugging. I have also used them pass
data in and out of functions as well.

Now, I was pointed out today that there is a difference. I was even
pointed out this is addressed in the standards.

What I would like to know is what is the proper way to handle APIs that
have functions with void ** parameters.

One interesting item brought up is that I might end up overwriting a
previously declared variable.
Anyway, I hope to learn more.

It's not at all clear what you're confused about. Parameters to
functions are defined to be of certain types, and you must pass
parameters of those types (or ones which get automatically converted to
those types) when you call the function. If the parameter type is 'int'
you must pass an int; if it's 'struct frooble *' you must pass a
pointer to a struct frooble; if it's 'void **' you must pass a pointer
to a pointer to void.
 
S

Snis Pilbor

In some APIs, we see char ** or void ** as a parameter. I never
distinguished between declaring a variable as char **x and passing x as
the parameter from declaring a variable char *x and passing &x. I also
consider the two the same. And since I debug everything I write, I
also saw know difference while debugging. I have also used them pass
data in and out of functions as well.

In my experience any time a function takes char ** or void **
arguments, it is for one of the following reasons:
1. the function depends not on a single char * or void * but on a
contiguous block of such, or
2. the function is meant to destructively alter the char * or void *,
for example a swap function

If the first usage is what is going on here, then that usage would not
realize its full potential unless some calling function declares the
variable as char **x and passes x. If in every single case, the
calling function declares the variable as char *x and passes &x, well,
then the fancy contiguous block code is kind of pointless (or should I
say "pointerless") since it only actually ever receives "contiguous
blocks" of size 1!

Nonetheless, I agree with you that there is no real difference.

Snis Pilbor
 
S

Snis Pilbor

Snis said:
In my experience any time a function takes char ** or void **
arguments, it is for one of the following reasons:
1. the function depends not on a single char * or void * but on a
contiguous block of such, or
2. the function is meant to destructively alter the char * or void *,
for example a swap function

If the first usage is what is going on here, then that usage would not
realize its full potential unless some calling function declares the
variable as char **x and passes x. If in every single case, the
calling function declares the variable as char *x and passes &x, well,
then the fancy contiguous block code is kind of pointless (or should I
say "pointerless") since it only actually ever receives "contiguous
blocks" of size 1!

Nonetheless, I agree with you that there is no real difference.

Snis Pilbor

Thinking about this a little more, I realized, you could in theory use
a void * to store the address to a whole contiguous buffer of void *'s.
This would probably be quite confusing, though I can see how in some
very highly obscure cases it could actually be indispensible. So in
the case of void, there is actually a third option, which is for the
caller to pass (void **) x, as so:

void **buf;
void *ptr;

buf = malloc( 100 * sizeof( void * ) );
ptr = buf;
nullify_pointer_array( (void **) ptr, 100 );

I wonder if such scandalous code as this would ever actually be useful,
or if it is just a funny example. (Of course in the above example,
"nullify_pointer_array(buf,100)" would do the same thing and be 100
times clearer)

S.P.
 
R

Richard Heathfield

(e-mail address removed) said:
In some APIs, we see char ** or void ** as a parameter. I never
distinguished between declaring a variable as char **x and passing x as
the parameter from declaring a variable char *x and passing &x. I also
consider the two the same.

Clearly, they aren't.

char *x;
char **y;
strtod(s, &x); /* &x is the address of an object of type char *. */
strtod(s, y); /* y is an indeterminate value */
And since I debug everything I write, I
also saw know difference while debugging.

You have now learned - I hope - that debugging is not sufficient. You have
to know what you're doing, too.

What I would like to know is what is the proper way to handle APIs that
have functions with void ** parameters.

It depends on what those functions are trying to do. Knowing the prototype
for a function is insufficient. You have to understand the semantics of
that function, too.

In the case of strtod, what it is trying to tell you is the address of a
character - the first character it couldn't convert. If it were written
like this: char *strtod(char *s); that would be an easier way for it to
tell you that address, but then it wouldn't be able to return the double!
And it can't just be written like this: double strtod(char *s, char
*endptr); because if it were, it wouldn't matter how much strtod tried to
change the value of endptr - it would have no effect whatsoever on the char
* that you passed in! That's because C is a pass-by-value language, always.
And so strtod's designer(s) went for the next option, which is to take the
address of a char * object. Why? So that they can store into that char *
object the address of the first character they couldn't convert. Now, for
that to be a legitimate thing to do, that object must ***exist***! And
that's why it's insufficient to use char **endptr; What you /could/ do is
this:

char *endptr;
char **intermediatevalue = &endptr;
double d = strtod(s, intermediatevalue);

and endptr now points to the first unconverted character. That code would
have just the same validity, because now the value you are passing to
strtod really is the address of a valid object.
One interesting item brought up is that I might end up overwriting a
previously declared variable.

The C Standard does not place limits on the possible outcomes of undefined
behaviour.
 
S

Snis Pilbor

Richard said:
(e-mail address removed) said:


Clearly, they aren't.

char *x;
char **y;
strtod(s, &x); /* &x is the address of an object of type char *. */
strtod(s, y); /* y is an indeterminate value */

I think when the OP said "declaring a variable as char **x and passing
x", it was implicitly assumed x would be initialized before being used.
I'm nitpicking here because I wouldn't want someone to be confused by
your post and think, "never declare char **y, it's automatically a Bad
Thing".

char *x;
char **y;

y = malloc( 50 * sizeof( char * ) );

nullify_char_pointers( y, 50 );
nullify_char_pointers( &x, 1 );

Here the fact we call nullify_char_pointers on &x is silly since we
could've just said "x=NULL", but the point is besides silliness there's
no difference between the two calls.

The OP was particularly concerned about passing with &x (where x is
char *) being more potentially destructive than passing with x (where x
is char **). At first I thought this was indeed a concern, but
reflecting on it, they are equally safe despite intuition. In order
for the function to act destructively, it would have to act on the
dereferenced input. If it does this, it will act destructively
regardless of how the input is passed, and if not, then not. I think.

S.P. =)
 
R

Richard Heathfield

Snis Pilbor said:
I think when the OP said "declaring a variable as char **x and passing
x", it was implicitly assumed x would be initialized before being used.

You think so? Then you might want to check out the original code he posted
that prompted this discussion:

Message-ID: <[email protected]>
 
B

bwaichu

Richard said:
You think so? Then you might want to check out the original code he posted
that prompted this discussion:

Message-ID: <[email protected]>

And he was completely right. I overlooked my approach thinking both of
the approaches were valid, but both approaches are clearly not valid.
But the process for me to understand that took some doing. The program
compiled fine. The program "worked". And my debug session worked out
fine. But what I was doing was wrong. The compiler did not even warn
me until I turned on an additional flag per the suggestion of one of
the folks in that thread. And that flag was an optimization flag, not
even warning related. This was a tough item to learn because
everything appeared to be working fine.

Thanks Richard. Sorry about my difficulty in learning this item.
Everything appeared to work and that made the learning process more
difficult.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top