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.