C function pointer casting to void pointer

23,834

Solution 1

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

Solution 2

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

Solution 3

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.

Solution 4

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.

Share:
23,834

Related videos on Youtube

Manoj
Author by

Manoj

Updated on July 09, 2022

Comments

  • Manoj
    Manoj almost 2 years

    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.

  • Manoj
    Manoj about 13 years
    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.
  • Manoj
    Manoj about 13 years
    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.
  • Jonathan Leffler
    Jonathan Leffler almost 11 years
    +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).
  • Jonathan Baldwin
    Jonathan Baldwin over 10 years
    "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.
  • rxantos
    rxantos about 10 years
    @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.
  • Jonathan Baldwin
    Jonathan Baldwin about 10 years
    @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.
  • Jarosław Bielawski
    Jarosław Bielawski about 8 years
    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.
  • vinc17
    vinc17 over 5 years
    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.
  • vinc17
    vinc17 over 5 years
    @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.
  • vinc17
    vinc17 over 5 years
    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.
  • Chris Dodd
    Chris Dodd about 5 years
    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.
  • Pascal Cuoq
    Pascal Cuoq about 5 years
    @ChrisDodd This is true. The question is tagged C, not POSIX, but I will add a note.
  • pmor
    pmor over 2 years
    @vinc17 Re: "I wonder why ...". Because before says "If a null pointer constant is converted to a pointer type ...". As I understand "pointer type" involves "pointer to function".
  • vinc17
    vinc17 over 2 years
    @pmor OK, there is a special rule that allows conversion of null pointers (6.3.2.3p4) and integers (6.3.2.3p5) to any pointer type. So 0 or any null pointer may be converted to any pointer type, and 6.3.2.3p3 says that one still gets a null pointer. And this applies in particular to null pointer constants.
  • pmor
    pmor over 2 years
    @vinc17 Per C null pointer "is guaranteed to compare unequal to a pointer to any object or function".
  • vinc17
    vinc17 over 2 years
    @pmor Yes, but you may first need to do a conversion to do comparisons between pointer types (thus it is important that a null pointer converts to a null pointer). For instance, the following is invalid: short *p = (void *) 0; int *q = (void *) 0; p == q;

Related