2

Found this code example

void *handle;
double (*cosine)(double);
handle = dlopen("libm.so", RTLD_LAZY);
*(void **) (&cosine) = dlsym(handle, "cos");

I use rightmost-to-left reading rule to parse the variable's type:

double (*cosine)(double);

Here I write left-to-right but move LTR: "cosine" -> "*" -> "is a pointer" then "(" we go outside the innermost () scope -> "(double)" -> "to function taking one double" -> and returning leftmost "double"

but what the hell is THIS? I even don't know where to start the parse from) is "&cosine" a address or reference? what does the (void **) mean? why it has leftmost "*" outside??? is it dereference or type?

*(void **) (&cosine)
barney
  • 2,172
  • 1
  • 16
  • 25

3 Answers3

7

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() .

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • wow ) so, when we cast the cosine var's address to this pointer-to-pointer-to-void, we have a "ordinary" "data" pointer, right? I mean will this "higher order" pointer to variable (which is a fpt) retain its guaranteed size of (void*)? Or the strongly typed pointer to fptr (&cosine) will remain "function pointer sized"? And where exactly a (theoretical) data loss could occur say in 16 bit DOS compiler? When we assign a void * (say 4 bytes) by address to function ptr's place (say segment:offset - 6 bytes) and we then would "underwrite" the memory cells? – barney Sep 22 '17 at 20:28
  • @barney `cosine` is a function pointer. `&cosine` is a data pointer (it points to the variable `cosine`). `(void **)(&cosine)` is a slightly different data pointer, that points to the memory where `cosine` is stored, but treats the pointed-to memory as a different type -- a `void *`, instead of a function pointer. So when we say `*(void **)(&cosine) = something;`, where's stomping on its bits as if it were a `void *`, which is fine *if* the size and representation of a `void *` is exactly the same as a function pointer. – Steve Summit Sep 22 '17 at 20:36
  • *I mean will this "higher order" pointer to variable (which is a fpt) retain its guaranteed size of `(void*)`?* The pointer (void **)(&cosine) is not a function pointer. It's a pointer-to-pointer (I guess that's what you meant by "higher order"), but what the outer, "higher level" pointer points to is a `void *`, not a function pointer. So yes, it's when we "assign a void * by address to function ptr's place" that we might do damage. – Steve Summit Sep 22 '17 at 20:40
  • The contortion `*(void **)` is listed in some man pages for `dlsym()`. Practically, compilers accept an explicit conversion from `void *` to a function pointer (as distinct from pointers to data, where the conversion is needed) and is the preferable form since it is not guaranteed that a `void *` has the same representation as a function pointer. I'm not aware of any compiler that ever gave a spurious warning though - so suspect the "going around the barn" (cute description!) resulted from someone being too smart or paranoid – Peter Sep 22 '17 at 22:31
6

This is nasty stuff. cosine is a pointer to a function that takes an argument of type double and returns double. &cosine is the address of that pointer. The cast says to pretend that that address is a pointer-to-pointer-to-void. The * in front of the cast is the usual dereference operator, so the result is to tell the compiler to pretend that the type of cosine is void*, so that the code can assign the return value from the call to dlsym to cosine. Phew; that hurts.

And, just for fun, a void* and a pointer-to-function are not at all related, which is why the code has to go through all that casting. The C++ language definition does not guarantee that this will work. I.e., the result is undefined behavior.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • The use of `dlsym` has always been "undefined behavior" per C and C++ standards. But guaranteed to work on POSIX systems. – P.P Sep 22 '17 at 19:46
  • @usr - yup. I started to mention that it works appropriately on systems where it's used, but decided to just continue disparaging it. "Undefined behavior" does not mean "bad things will happen". I've never coded that stuff, but at first glance, I'd just cast the return from `dlsym` to `double(*)(double)`. Still undefined, but clearer, and still works wherever it works. – Pete Becker Sep 22 '17 at 19:51
  • Yeah, as far as I know "pointer to function" is even not guaranteed to be same size as void * – barney Sep 22 '17 at 20:09
2

For C, a pointer to void can be converted to any pointer to an object without a cast. However, the C standard does not guarantee that void * can be converted to a pointer to a function - at all, since functions are not objects.

The dlsym is a POSIX function; and POSIX requires that as an extension, a pointer to a function must be convertable to void * and back again. However C++ wouldn't allow such a conversion without a cast.

In any case the *(void **) (&cosine) = dlsym(handle, "cos"); cast means that the pointer to the pointer to a function (double) returning double is cast as pointer to pointer to void, then dereferenced to get a lvalue of type void *, to which the return value of dlsym is assigned to. This is rather ugly, and should be better written as cosine = (double (*)(double))dlsym(handle, "cos") wherever a cast is required. Both are undefined behaviour when it comes to C, but the latter is not as much dark magic.