Yup, that's a mouthful.
cosine
is a function pointer. So &cosine
is a pointer to that pointer. And then when we slap a *
in front of it, we're changing the original pointer, to make it point somewhere else.
It's sort of like this:
int i = 5;
int *ip = &i;
*ip = 6; /* changes i to 6 */
Or it's more like this:
char a[10], b[10];
char *p = a;
*(&p) = b; /* changes p to point to b */
But in your case it's even trickier, because cosine
is a pointer to a function, not a pointer to data. Most of the time, function pointers point to functions you have defined in your program. But here, we're arranging to make cosine
point to a dynamically-loaded function, loaded by the dlsym()
function.
dlsym
is super special because it can return pointers to data, as well as pointers to functions. So it's got an impossible-to-define return type. It's declared as returning void *
, of course, because that's the "generic" pointer type in C. (Think malloc
.) But in pure C, void *
is a generic data pointer type; it's not guaranteed to be able to be used with function pointers.
The straightforward thing to do would be to just say
cosine = dlsym(handle, "cos");
But a modern compiler will complain, because dlsym
returns void *
, and cosine
has type double (*)(double)
(that is, pointer to function taking double and returning double), and that's not a portable conversion.
So we go around the barn, and set cosine
's value indirectly, not by saying
cosine = something
but rather by saying
*(&cosine) = something
But that's still no good in the dlsym
case, because the types still don't match. We've got void *
on the right, so we need void *
on the left. And the solution is to take the address &cosine
, which is otherwise a pointer-to-pointer-to-function, and cast it to a pointer-to-pointer-to-void
, or void **
, so that when we slap a *
in front of it we've got a void *
again, which is a proper destination to assign dlsym
's return value to. So we end up with the line you were asking about:
* (void **) (&cosine) = dlsym(handle, "cos");
Now, it's important to note that we're on thin ice here. We've used the &
and the cast to get around the fact that assigning a pointer-to-void
to a `pointer-to-function isn't strictly legal. In the process we've successfully silenced the compiler's warning that what we're doing isn't strictly legal. (Indeed, silencing the warning was precisely the original programmer's intent in employing this dodge.)
The potential problem is, what if data pointers and function pointers have different sizes or representations? This code goes to some length to treat a function pointer, cosine
, as if it were a data pointer, jamming the bits of a data pointer into it. If, say, a data pointer were somehow bigger than a function pointer, this would have terrible effects. (And, before you ask "But how could a data pointer ever be bigger than a function pointer?", that's exactly how they were, for example, in the "compact" memory model, back in the days of MS-DOS programming.)
Normally, playing games like this to break the rules and shut off compiler warnings is a bad idea. In the case of dlsym
, though, it's fine, I would say perfectly acceptable. dlsym
can't exist on a system where function pointers are different from data pointers, so if you're using dlsym
at all, you must be on a machine where all pointers are the same, and this code will work.
It's also worth asking, if we have to play games with casts when calling dlsym
, why take the extra trip around the barm with the pointer-to-pointer? Why not just say
cosine = (double (*)(double))dlsym(handle, "cos");
And the answer is, I don't know. I'm pretty sure this simpler cast will work just as well (again, as long as we're on a system where dlsym
can exist at all). Perhaps there are compilers that warn about this case, that can only be tricked into silence by using the tricker, double-pointer trick.
See also Casting when using dlsym() .