C function pointer casting to void pointer
I am trying to run the following program but getting some strange errors:
File 1.c:
typedef unsigned long (*FN_GET_VAL)(void);
FN_GET_VAL gfnPtr;
void setCallback(const void *fnPointer)
{
gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}
File 2.c:
extern FN_GET_VAL gfnPtr;
unsigned long myfunc(void)
{
return 0;
}
main()
{
setCallback((void*)myfunc);
gfnPtr(); /* Crashing as value was not properly
assigned in setCallback function */
}
Here the gfnPtr() is crashing on 64-Bit suse l开发者_开发技巧inux when compiled with gcc. But it successfully calling gfnPtr() VC6 and SunOS.
But if I change the function as given below, it is working successfully.
void setCallback(const void *fnPointer)
{
int i; // put any statement here
gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}
Please help with the cause of problem. Thanks.
The C standard does not allow to cast function pointers to void*
. You may only cast to another function pointer type. In the C11 standard, 6.3.2.3 §8:
A pointer to a function of one type may be converted to a pointer to a function of another type and back again
Importantly, you must cast back to the original type before using the pointer to call the function (technically, to a compatible type. Definition of "compatible" at 6.2.7).
Note that the POSIX standard, which many (but not all) C compilers have to follow too because of the context in which they are used, mandates that a function pointer can be converted to void*
and back. This is necessary for some system functions (e.g. dlsym
).
The standard unfortunately doesn't allow casting between data pointers and function pointers (because that might not make sense on some really obscure platforms), even though POSIX and others require such casts. One workaround is not to cast the pointer but to cast a pointer to the pointer (this is OK by the compiler and it will get the job done on all normal platforms).
typedef void (*FPtr)(void); // Hide the ugliness
FPtr f = someFunc; // Function pointer to convert
void* ptr = *(void**)(&f); // Data pointer
FPtr f2 = *(FPtr*)(&ptr); // Function pointer restored
I've got three rules of thumb when it come to data pointers and code pointers:
- Do not mix data pointers and code pointers
- Do not mix data pointers and code pointers
- Do not ever mix data pointers and code pointers!
In the following function:
void setCallback(const void *fnPointer)
{
gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}
You have a data pointer that you case to a function pointer. (Not to mention that you do this by first taking the address of the pointer itself, cast it to a pointer to a pointer, before de-referencing it).
Try to rewrite it as:
void setCallback(FN_GET_VAL fnPointer)
{
gfnPtr = fnPointer;
}
Also, you can (or should) drop the cast when setting the pointer:
main()
{
setCallback(myfunc);
gfnPtr();
}
As an extra bonus, you could now use the normal type checks performed by the compiler.
I will suggest a possible partial explanation.
@Manoj If you examine (or can provide) the assembly listing for SetCallback generated by both compilers, we can get a definitive answer.
Firstly, Pascal Couq's statements are correct, and Lindydancer shows how to correctly set the callback. My answer is only an attempt at explaining the actual problem.
I think the problem stems from the fact that Linux and the other platform use different 64-bit models (see 64-bit models on Wikipedia). Note that Linux uses LP64 (int is 32 bit). We need more detail on the other platform. If it is SPARC64, it uses ILP64 (int is 64 bit).
As I understand you, the problem was only observed under Linux, and went away if you introduced an int local variable. Did you try this with optimisations off or on? Most likely this hack would have no beneficial effect with optimisations on.
Under both 64-bit models, pointers should be 64-bit, regardless of whether they point to code or data. However, it is possible that this would not be the case (e.g. segmented memory models); hence, Pascal and Lindydancer's admonisions.
If the pointers are the same size, what remains is a possible stack alignment issue. Introducing a local int (which is 32 bit under Linux) could alter alignment. This would only have an effect if void* and function pointers have different alignment requirements. A doubtful scenario.
Nevertheless, the different 64-bit memory models are most likely the cause of what you observed. You are welcome to provide the assembly listings so that we can analyse them.
unlike what others say, yes you can have a void*
pointer as a function pointer but the semantics are very tricky to use it with.
As you can see, you don't NEED to cast it as a void*
, just assign it like normal. I ran your code like this and I edited it to work.
file1.c:
typedef unsigned long (*FN_GET_VAL)(void);
extern FN_GET_VAL gfnPtr;
void setCallback(const void *fnPointer)
{
gfnPtr = ((FN_GET_VAL) fnPointer);
}
file2.c:
int main(void)
{
setCallback(myfunc);
(( unsigned long(*)(void) )gfnPtr)();
}
精彩评论