31

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 linux 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.

5 Answers 5

51

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

Sign up to request clarification or add additional context in comments.

10 Comments

Thanks for your responses. I would take care not mix data and function pointers. But in this case I am not able to figure out the reason why this is happening. If I compile and run with -m32 (32 Bit) it is working perfectly fine but if I compile with -m64 (64Bit) it is giving the problem. Also if I add a single statement like int i; above assignment then it is working fine. Not sure about the reason maybe stack corruption but how to check.
I agree that the standard does not allow to cast function pointers to void*, according to 6.3.2.3; but I wonder why 6.3.2.3p3 says "to a pointer to any object or function" since this must not occur. BTW, J.5.7 says "A pointer to an object or to void may be cast to a pointer to a function, allowing data to be invoked as a function" and the reverse, but that's J.5 Common extensions, thus not portable.
@Manoj The reason might be some optimization (even when compiling without -O). Perhaps at some place, the compiler thinks that function myfunc is not used, or something like that.
It depends on which standard you are using -- the POSIX standard mandates that the C compiler support casting function pointers to void * and back again safely.
@ChrisDodd This is true. The question is tagged C, not POSIX, but I will add a note.
|
14

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

3 Comments

"because that might not make sense on some really obscure platforms" Really obscure platforms like Linux, Mac OS X, iOS, Microsoft Windows and Android, which have Data Execution Prevention. With Data Execution Prevention, code and data are NOT interchangeable. Note: POSIX requires the cast to void pointer, but it does not require that such a resulting pointer be usable except for converting back into a function pointer.
@JonathanBaldwin What you say only makes sense on platforms in which the code pointer have a different size than the data pointer. A pointer is just an address and as such is never in the executable area. However the data pointed by the address might be on a executable area. So you might not be able to read the memory pointed by the pointer. But you can certainly cast it to from function pointer to void * and from void * to function pointer using this method. Unless the platform has a different size for function pointers than data pointers it will work.
@rxantos Sure, a cast from data to function pointer would be trivial to implement on a ia32 system with DXP, but it doesn't make sense because such a pointer becomes unusable without casting it back to a data pointer or using a system call to punch holes in DXP (ie. make the area executable.) As far as C is concerned, supporting this is not worth providing inconsistent support for the platforms you mentioned, which included 16-bit DOS and Windows (remember near and far pointers?) After all, if a platform, such as POSIX, wants to allow this directly, they could make it an extension.
11

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.

2 Comments

Thanks for your responses. I would take care not mix data and function pointers. But in this case I am not able to figure out the reason why this is happening. If I compile and run with -m32 (32 Bit) it is working perfectly fine but if I compile with -m64 (64Bit) it is giving the problem. Also if I add a single statement like int i; above assignment then it is working fine. Not sure about the reason maybe stack corruption but how to check.
+1 for the analysis that taking the address of a function pointer is not the same as applying & to a function name (which is a no-op).
3

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.

1 Comment

The wikipedia entry you link to is wrong. The 64 bits ABI for Solaris on Sparc is LP64 not ILP64. Btw SPARC64 is a brand name for Fujitsu's Sparc implementation exclusively.
-1

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)();
}

1 Comment

An assignment involves a conversion, just like with a cast (which is by definition an explicit conversion), thus this is undefined behavior. This may work with some C implementations, either as an extension or just by chance (e.g. because of the same representation and no optimizations that will break the code), but there is no guarantee from the C standard.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.