Is there such a thing as a generic function pointer in C that can be assigned/cast to a more restrictive prototype?
Asked Answered
H

3

22

I have the need to dynamically link against a library at run-time and resolve a series of functions using dlsym. My first thought was to use an array of function pointers that can be easily iterated through by leveraging a secondary array of char * representing the symbol names.

However, the problem with this is that not all of the functions take the same arguments. Is there a way to use a generic function pointer in the array, but assign it to a more restrictive function pointer prototype? For example:

If I need to resolve these functions:

int (*functionA)(int)
char (*functionB)(char,int)

Is it possible to do something like (pseudo-ish .. )

void* functionList(...)[2] = {functionA, functionB};

Along with

char FuncNameA[] = "functionA";
char FuncNameB[] = "functionB";
char *functionNames[2] = {FuncNameA, FuncNameB};

For the purpose of looping though the call to dlsym for symbol resolution

int i = 0;
for(; i<2; i++)
    functionList[i] = dlsym(DL_OPEN_HANDLE, functionNames[i]);

Where DL_OPEN_HANDLE would be defined by an earlier call to dlopen.

Harmaning answered 17/7, 2015 at 18:43 Comment(4)
@edtheprogrammerguy, I have no idea. If it were possible I figured it would be something like void* (*func)(void*, ...) , or similar.Harmaning
@edtheprogrammerguy: void* is likely to work, but not guaranteed by the C standard. All object pointer types can be converted to void* and back again without loss of information, but there's no such guarantee for converting function pointer types to void*.Salley
It is a very delicate area. The POSIX spec was changed relatively recently. See 0000074: Pointer types problem for POSIX's view on the situation.Dracula
Related question: Why are function pointers and data pointers incompatible in C/C++?.Oncoming
S
25

The C standard guarantees that any object pointer type can be converted to void* and back again without loss of information (meaning that the re-converted pointer will compare equal to the original one).

There is a different guarantee for function pointers: Any function pointer can be converted to any other function pointer type and back again without loss of information.

(There is no guarantee regarding conversions between function pointers and object pointers, or more specifically between function pointers and void*. An implementation could, for example, make void* 64 bits and function pointers 128 bits.)

You can use, for example, void(*)(void) as a generic function pointer type:

typedef void (*funcptr)(void);

You must convert back to the original pointer type before executing a call to avoid undefined behavior.

On the other hand, you're using dlsym(), which returns a void*. My understanding is that POSIX guarantees that the void* returned by dlsym() (if the name argument names a function) can be converted to a function pointer, which can be used to call the function. If the only functions you care about are those whose addresses are returned by dlsym(), then you can use void*.

(POSIX previously guaranteed, as an extension to the C standard, that a function pointer can be converted to void* and back again. That guarantee was later dropped. Thanks to Jonathan Leffler for pointing this out.)

In any case, using function pointers to store the addresses of functions probably makes for clearer code.

Salley answered 17/7, 2015 at 18:56 Comment(6)
Sadly, although it used to, it doesn't any more. Section 2.12.3 provided that guarantee up to POSIX 1003.1:2008, but the revision POSIX 1003.1:2013 removed that subsection. See also 0000074: Pointer Types Problem — also mentioned in a comment to the question — for the details. Frankly, I'm not sure that what we've got now is better than what we had before.Dracula
@JonathanLeffler: So POSIX still guarantees that a void* returned by dlsym() can be converted to a function pointer (if the name argument names a function), but does not guarantee that an arbitrary function pointer can be converted to void* and back again; is that correct?Salley
AFAICT, yes. Which is pretty damn weird, but … who said standards had to be rational?Dracula
Good answer. I'll +1 if you put "any function pointer can be converted to any other function pointer type and back again without loss of information" first, and only then say that there is no guarantee that a function pointer can be converted into a void * and back. Remember - answers are "cognition funnels", but some people are in a hurry, skimming, or are just bad at reading, so you want to help move the minds of readers towards the answer as quickly and clearly as possible, and then elaborate after that.Plumule
@KeithThompson Cool, thanks. I was imagining pulling "Any function pointer can be converted to any other function pointer type ..." up to be the very first sentence of the whole answer, and then transitioning into what you said about void* with something like "Not to be confused with the similar guarantee about void*, which only applies to object pointer types. An implementation could, for example, make void* 64 bits and function pointers 128 bits." But this is already an improvement, so +1.Plumule
I'm thinking it would be legal to type pun union { void *obj; void (*func)(void) } if you need generics across both data and functions.Saloop
G
5

You should probably define your list as void *functionList[2], since dlsym returns a void *. Once you know which function you have, you can cast it to the proper type.

void *functionList[2];

...

int (*functionA)(int) = (int(*)(int))functionList[0];
char (*functionB)(char,int) = (char(*)(char, int))functionList[1];
Gascon answered 17/7, 2015 at 18:51 Comment(1)
I guess this is what I thinking, albeit backwards for some reason. Thanks!Harmaning
K
5

dlsym returns a data pointer of type void *, but POSIX guarantees that this can be cast to a function pointer of the appropriate type:

Implementations supporting the XSI extension [...] require that an object of type void * can hold a pointer to a function. The result of converting a pointer to a function into a pointer to another data type (except void *) is still undefined, however.

Since Version 7 of POSIX, all implementations (not just XSI) are required to support the conversion.

Because conversion from a void * pointer to a function pointer via a direct cast can result in compiler warnings, older versions of POSIX recommend performing the conversion via aliasing:

int (*fptr)(int);
*(void **)(&fptr) = dlsym(handle, "my_function");

In the current version the recommendation is changed to:

int (*fptr)(int);
fptr = (int (*)(int))dlsym(handle, "my_function");
Karmenkarna answered 17/7, 2015 at 18:59 Comment(2)
Geez, that former syntax is ugly. The latter syntax is actually readable; I have no idea how to interpret the former.Harmaning
@Harmaning it's aliasing fptr with a void* pointer by casting a pointer to fptr to a pointer to pointer to void, dereferencing it, and then assigning to the result. As you say, it's ugly; you may see it in even relatively recent code, though.Karmenkarna

© 2022 - 2024 — McMap. All rights reserved.