Confused about "void **"

S

sunglo

My doubt comes from trying to understand how thread return values work
(I know, it's off topic here), and I'm wondering about the meaning of
the "void **" parameter that pthread_join expects (I think this is
topical, since it's a C question, but please correct me and apologies
if I'm wrong).

I suppose that it's this way to allow for "generic" pointer
modification, but this implies dereferencing the "void **" pointer to
get a "void *" one. So, is this allowed or does the implementation have
to play some trick somewhere, like for example casting the void ** to
another type (for example, int ** on a 32 bit machine) that can then be
safely dereferenced?

Thanks
 
R

REH

My doubt comes from trying to understand how thread return values work
(I know, it's off topic here), and I'm wondering about the meaning of
the "void **" parameter that pthread_join expects (I think this is
topical, since it's a C question, but please correct me and apologies
if I'm wrong).

I suppose that it's this way to allow for "generic" pointer
modification, but this implies dereferencing the "void **" pointer to
get a "void *" one. So, is this allowed or does the implementation have
to play some trick somewhere, like for example casting the void ** to
another type (for example, int ** on a 32 bit machine) that can then be
safely dereferenced?

Thanks

The function is expecting you to give it a pointer to void that it is going
to set to something. If a function takes an object of type T that it must
modify for the caller, it must be given a pointer to the object, thus T*.
The function wants to modify a void*, so if T is void*, then T* is void**

I hope that was clear enough.

REH
 
E

Eric Sosman

My doubt comes from trying to understand how thread return values work
(I know, it's off topic here), and I'm wondering about the meaning of
the "void **" parameter that pthread_join expects (I think this is
topical, since it's a C question, but please correct me and apologies
if I'm wrong).

I suppose that it's this way to allow for "generic" pointer
modification, but this implies dereferencing the "void **" pointer to
get a "void *" one. So, is this allowed or does the implementation have
to play some trick somewhere, like for example casting the void ** to
another type (for example, int ** on a 32 bit machine) that can then be
safely dereferenced?

No tricks. You cannot dereference a `void*' because
it points to what's called an "incomplete type" -- loosely
speaking, it points to the start of a piece of anonymous
memory of unknown size and significance. You know where
the memory is, but you don't know how big it is or how to
interpret its contents -- in short, you don't know how to
"refer" to it.

However, a `void*' itself is a perfectly normal data
object, just like an `int' or a `double*'. Its size is
known and its representation is known, so if you know that
a certain piece of memory holds a `void*' you have enough
information to fetch or store a `void*' value in that memory.
And what kind of a pointer points to a `void*'? A `void**',
of course. There's no trickery about it.

However, this doesn't mean a `void**' is a "generic
pointer to pointer" in the sense that `void*' is frequently
called a "generic pointer." A `void*' can point to any kind
of data object (because it will not be dereferenced), but
a `void**' can only properly point to a `void*' -- it can't
point to an `int*' or a `double*'. (There's a special rule
that allows it to point to a `char*', but that rule exists
only to legitimize pre-Standard code, and is best ignored.)

What this means (we're straying near the frontiers of
topicality here, but your question is a bona-fide C question
even though it involves beyond-C API's) is that

- Your thread function creates a data object containing
its Final Answer (taking care to use an object that
will survive the thread's demise), takes a pointer
to that object and converts it to `void*' (the
conversion is automatic, but it occurs), and returns
that `void*' value.

- The thread_join() function locates the `void*' value
returned by the defunct thread, and wants to put it
somewhere for your inspection. To receive the value,
you create a `void*' variable somewhere and pass a
pointer to it -- a `void**' -- to thread_join(), which
plunks the value in the place you indicate.

- Now you've got the `void*' that the thread returned,
but you can't do much with it because you can't
dereference it. However, you know the type of the
data object that holds the thread's Final Answer, so
you can convert the `void*' to a pointer to that data
type (again, the conversion is automatic), and with
that pointer you can access to the thread's Last Will
and Testament.

Pseudocode outline:

struct final_answer { int this; double that; };
...
void *thread(...) {
struct final_answer *adios = malloc(sizeof *adios);
...
adios->this = 42;
adios->that = 12e34;
return adios;
}
...
void *raw_result;
struct final_answer *result;
thread_join(..., &raw_result);
/* This ^^^^^^^^^^^ is the `void**' */
result = raw_result;
printf ("%d, %g\n", result->this, result->that);

The `raw_result' variable is necessary here; you cannot
just pass `&result' as the thread_join() argument.
 
S

sunglo

Eric said:
No tricks. You cannot dereference a `void*' because
it points to what's called an "incomplete type" -- loosely
speaking, it points to the start of a piece of anonymous
memory of unknown size and significance. You know where
the memory is, but you don't know how big it is or how to
interpret its contents -- in short, you don't know how to
"refer" to it.
[cut]

Great, a lot more than what I asked! Thanks.
Pseudocode outline:

struct final_answer { int this; double that; };
...
void *thread(...) {
struct final_answer *adios = malloc(sizeof *adios);
...
adios->this = 42;
adios->that = 12e34;
return adios;
}
...
void *raw_result;
struct final_answer *result;
thread_join(..., &raw_result);
/* This ^^^^^^^^^^^ is the `void**' */
result = raw_result;
printf ("%d, %g\n", result->this, result->that);

The `raw_result' variable is necessary here; you cannot
just pass `&result' as the thread_join() argument.

I have a feeling that passing (void **)&result instead is not strictly
correct (although it might work), but I can't exactly figure out
why...can you help me with this too?

Thanks
 
E

Eric Sosman

I have a feeling that passing (void **)&result instead is not strictly
correct (although it might work), but I can't exactly figure out
why...can you help me with this too?

It would be incorrect, for the same reason that

int *ptr;
double val = strtod("123.45%", (char**)&ptr);

is incorrect. strtod() wants to store a value into a
`char*' variable, and giving it an `int*' (even if
disguised) is asking for trouble. thread_join() wants
to store a value into a `void*', and giving it a
`struct final_answer*' (even if disguised) is asking
for trouble.

"A rose is a rose is a rose," but it does not follow
that "a pointer is a pointer is a pointer." Pointers
need not all have the same size, nor need they all assign
the same meaning to each bit of their representations;
pointers of different types are not interchangeable
except in a few special cases (of which this is not one).
An `int' and a `double' can occupy different amounts of
memory and have different bit patterns even though they
both represent the value forty-two; just so, a `void*'
and a `struct final_answer*' can occupy different amounts
of memory and have different bit patterns even though they
both point to the same data object.
 
S

sunglo

Eric said:
"A rose is a rose is a rose," but it does not follow
that "a pointer is a pointer is a pointer." Pointers
need not all have the same size, nor need they all assign
the same meaning to each bit of their representations;
pointers of different types are not interchangeable
except in a few special cases (of which this is not one).
An `int' and a `double' can occupy different amounts of
memory and have different bit patterns even though they
both represent the value forty-two; just so, a `void*'
and a `struct final_answer*' can occupy different amounts
of memory and have different bit patterns even though they
both point to the same data object.

You are absolutely correct. The point is, if someone (like me)
always works on machines where pointers happen to have all
the same size and representation, which happen to be the same
size and representation of ints, the above problems are not
always taken into consideration. Nonetheless, they are real
and must be considered when portability is needed.

Thanks again to you and all the others who replied.
 
K

Keith Thompson

My doubt comes from trying to understand how thread return values work
(I know, it's off topic here), and I'm wondering about the meaning of
the "void **" parameter that pthread_join expects (I think this is
topical, since it's a C question, but please correct me and apologies
if I'm wrong).

I suppose that it's this way to allow for "generic" pointer
modification, but this implies dereferencing the "void **" pointer to
get a "void *" one. So, is this allowed or does the implementation have
to play some trick somewhere, like for example casting the void ** to
another type (for example, int ** on a 32 bit machine) that can then be
safely dereferenced?

A quick summary:

A void* is a generic pointer.

A void** is not a generic pointer-to-pointer (C has no such thing);
it's a pointer-to-generic-pointer.
 

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

No members online now.

Forum statistics

Threads
474,161
Messages
2,570,892
Members
47,428
Latest member
RosalieQui

Latest Threads

Top