Thread: [33.8] Can I convert a pointer-to-function to a void*?

  1. #1
    Registered User
    Join Date
    Oct 2008
    Posts
    1,262

    [33.8] Can I convert a pointer-to-function to a void*?

    I came across this FAQ: [33] Pointers to member functions ..Updated!.., C++ FAQ Lite

    One of the questions is
    [33.8] Can I convert a pointer-to-function to a void*?
    And they answer this with a "no, it's illegal". First my question: is it really illegal? I wouldn't have thought so. Let me explain why.

    First let's consider C. We're free to convert any type of pointer to a void pointer without casting, and we're free to convert it back without any problems. So in C, this would definitely seem legal. So I would derive from that that on any architecture a pointer is a compatible type with a void pointer. That is, they are always the same size (or a void pointer is always the largest type, so it can hold any other type of pointer).

    Now in C++ it was changed so you can no longer cast implicitly to or from void pointers. However, as far as I know, the underlying structure of the pointers still follow the same rules and thus it should still be legal to my understanding to cast a function pointer to a void pointer and back to a function pointer.
    I believe that the implicit casting from/to void* was disabled because of classes, because it could result in very strange bugs if you would allow it, bugs that now require explicit casting.

    Besides, I still see loads of APIs where you have to cast arguments to void*. pthread_create is an example. Even though it's a C API, it is assumed to be completely compatible with C++ as well, which it wouldn't be if you can not cast a pointer-to-function to a void*.

    So, again: Can I "convert" (cast) a pointer-to-function to a void*? If not, could you explain what the C++ standard says that makes it illegal?


    Thanks
    Last edited by EVOEx; 08-17-2009 at 06:41 AM.

  2. #2
    The larch
    Join Date
    May 2006
    Posts
    3,573
    The FAQ goes on to say that it's illegal to cast even pointer-to-function to void*. Besides pthread_create (which I haven't used) seems to accept a void *(*start_routine)(void*) which is a different thing from void*. (I assume that through the void* argument you can smuggle in a struct that contains an object and a member function pointer?)
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  3. #3
    Registered User
    Join Date
    Oct 2008
    Posts
    1,262
    Quote Originally Posted by anon View Post
    The FAQ goes on to say that it's illegal to cast even pointer-to-function to void*. Besides pthread_create (which I haven't used) seems to accept a void *(*start_routine)(void*) which is a different thing from void*. (I assume that through the void* argument you can smuggle in a struct that contains an object and a member function pointer?)
    I know the FAQ says it's illegal, I just can't see why. And I'm talking about the argument to pthread_create. If you can cast a structure pointer to a void pointer, why not a function pointer? And the rest of my post still holds with why I disagree with the FAQ. But I might be missing something, that is basically my question: what am I missing?

  4. #4
    Registered User
    Join Date
    Oct 2001
    Posts
    2,129
    first thing you should do is separate the concepts of conversions and casts. casts are syntactic elements like (type) that perform a conversion. a conversion might or might not change the representation or value.

    when you assign void* to another pointer type or back again, you don't need a cast, but a conversion does happen. the only type that is required to have the same representation as void* is char*, so a change to the representation converting to char* from void* or vice versa would not happen. conversions to/from other pointer types could have a change in representation.

    also, 'compatible' is a term used in the C standard, and it's not how you're using it.

    you can't convert pointers to functions to void* because void* is only for object (aka data) types. I think it was made like that because of harvard architecture processors, where code is on one bus and data is on another bus. the two buses could have different address widths.

    posix and windows, afaik, guarantee that you can convert a pointer to function to void* and back.

  5. #5
    Registered User
    Join Date
    Oct 2008
    Posts
    1,262
    So let me get this straight...
    1. With pthread_create, the argument to the thread function, the only parameter you can legally specify is a character array, as only they are required to have the same width?
    2. malloc can't be used in C++ except for character arrays. I know it's deprecated anyway, but it's even illegal? As "malloc(sizeof(SomeStruct))" won't work as the void pointer it returns may have a different width than the SomeStruct pointer?

    If that's true, that means that so many APIs and applications are just wrong (okay, they are anyway). And so many APIs are useless in C++ if they are written for C.

    And I won't even get started on mixing C++ with C. In fact, wouldn't it make it possible to write a function in C which does the void* to pointer-to-function conversion for you, and then link it with C++? Like:
    Code:
    typedef void (*func_ptr)();
    
    void *convertFuncPtrToVoidPtr(func_ptr f)
    {
      return f;
    }
    
    func_ptr convertVoidPtrToFuncPtr(void *v)
    {
      return v;
    }
    And there, we can legally do what we couldn't with just C++.


    So it still doesn't make much sense to me.


    Thanks for your replies though.

  6. #6
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by EVOEx
    First let's consider C. We're free to convert any type of pointer to a void pointer without casting, and we're free to convert it back without any problems.
    The 1999 edition of the C standard disagrees with you:
    Quote Originally Posted by C99 Section 6.3.2.3 Paragraph 1
    A pointer to void may be converted to or from a pointer to any incomplete or object type. A pointer to any incomplete or object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.
    Quote Originally Posted by C99 Section 6.3.2.3 Paragraph 8
    A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  7. #7
    The larch
    Join Date
    May 2006
    Posts
    3,573
    I have been under the impression that casting objects to void* and back to the same type is OK. However, casting void* to pointer-to-member-function doesn't even compile (with GCC).

    According to this understanding, something like the following should be OK (the member function pointer is not cast anywhere):

    Code:
    #include <iostream>
    class Fred
    {
    public:
        int f(char x, float y) { std::cout << x << ' ' << y << '\n'; }
    };
    
    // FredMemFn points to a member of Fred that takes (char,float)
    typedef  int (Fred::*FredMemFn)(char x, float y);
    
    #define CALL_MEMBER_FN(object,ptrToMember)  ((object)->*(ptrToMember))
    
    struct FredAndMethod
    {
        Fred* fred;
    
        FredMemFn fun;
        float y;
        char x;
    };
    
    void fred_caller(void* data)
    {
        FredAndMethod* fm = static_cast<FredAndMethod*>(data);
        CALL_MEMBER_FN(fm->fred, fm->fun)(fm->x, fm->y);
    }
    
    void callit(void (*f)(void*), void* data)
    {
        f(data);
    }
    
    int main()
    {
        Fred o;
        FredAndMethod fm = { &o, &Fred::f, 3.14f, 'x' };
        callit(fred_caller, &fm);             // okay
    }
    Of course, I might be wrong, but even Stroustrup in his technical FAQ shows casting void* to int*.
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  8. #8
    Registered User
    Join Date
    Oct 2008
    Posts
    1,262
    Quote Originally Posted by laserlight View Post
    The 1999 edition of the C standard disagrees with you:
    Actually, from what I see from that, the C standard agrees with me. Let's say we have two function types A and B (where A != B). We convert A to void*, then to B. We then call the dereference of this B-pointer. This is illegal. That's how I interpret the standard.
    However the rest of your snippets say that if you compare A to void* and then back to A, then call the dereference of this A-pointer. This is apparently legal according to what you copy. So:
    Code:
    typedef void (*A)();
    typedef int (*B)();
    
    void test() {}
    
    int main()
    {
      void *tmp;
      A *a;
      B *b;
    
      a = &test;
      tmp = a;
      b = tmp;
      a = tmp;
    
      // Here, calling a is legal, but calling b is illegal
    }
    But the FAQ shows where a is called again, which would be legal in C, if I understand the snippets from the standard correctly.

    So thanks for your answer, but my questions and confusions still stand.



    @anon:
    Thanks, but the following compiles just fine in GCC:
    Code:
    #include <stdio.h>
    
    int main()
    {
      void (*f)();
      void *tmp;
    
      tmp = f;
    }
    Last edited by EVOEx; 08-17-2009 at 08:18 AM.

  9. #9
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by EVOEx
    Let's say we have two function types A and B (where A != B). We convert A to void*, then to B.
    You cannot even legally convert A to void* since A is not "a pointer to any incomplete or object type".
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  10. #10
    Registered User
    Join Date
    Oct 2001
    Posts
    2,129
    you haven't separated the concepts of casts and conversions correctly.

    void *v = malloc(sizeof v); no conversion, no cast
    int *i = malloc(sizeof i); conversion, no cast, possible change in representation
    char *c = malloc(sizeof c); conversion (but no change in representation), no cast

    int *i = (int*)malloc(sizeof i); conversion, cast, possible change in representation
    char *c = (char*)malloc(sizeof c); conversion (but no change in representation), cast

    1. With pthread_create, the argument to the thread function, the only parameter you can legally specify is a character array, as only they are required to have the same width?
    For the void* parameter, you can pass any void*. in C, any pointer to object (not pointer to function) would get implicitly converted to a void. in C++, you'd have to explicitly convert it with a cast.

    int *my_data;
    pthread_t *pt;
    pthread_arr_t *pa;
    void *routine (void*);
    pthread_create(pt, pa, &routine, my_data); /* in C */
    pthread_create(pt, pa, &routine, (void*)my_data); /* in C++ */

    2. malloc can't be used in C++ except for character arrays. I know it's deprecated anyway, but it's even illegal? As "malloc(sizeof(SomeStruct))" won't work as the void pointer it returns may have a different width than the SomeStruct pointer?
    it would work if you casted the void* returned from malloc into the type of pointer you wanted. that would make a conversion happen, and it would be ok. (the conversion would be ok because malloc returns a pointer suitably aligned for any builtin type, and you can convert a pointer to another pointer type if the pointer's value is correctly aligned for the pointed-to type of the converted-to type)
    Last edited by robwhit; 08-17-2009 at 12:57 PM.

  11. #11
    Registered User
    Join Date
    Oct 2008
    Posts
    1,262
    Ahhh, I missed (at both robwhit's and laserlight's post) the point where an explicit distinction was made between function pointers and other pointers. It makes a lot more sense now.
    And I do know the difference between conversion and cast but I'm talking about a cast because I'm talking about C++ here. And to get a conversion from one pointer type to another, you have to use a cast (with the exception of inheritance).

    However, that means that while it's illegal to cast to void*, it's perfectly legal to cast it to void (*)()? Because that's probably what people want to cast to void* for (I don't even want to do it, when I don't fully understand something I just want to know for in the future): to be able to cast it back later and call it. From what I've seen it usually goes along with another parameter like function_type or num_args.

  12. #12
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    pthread_create is flawed. Or at least it seems so to me.
    It would need to convert that void* to a valid pointer type before calling it anyway, so why not take a correct type from the beginning? In C, there is no way to take an infinite number of function types in any reliable way to my knowledge.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  13. #13
    Registered User
    Join Date
    Oct 2001
    Posts
    2,129
    it's void* so you can pass a pointer to anything to it. if it was a narrower type, you'd have to have multiple pthread_create functions to have full coverage over all the possible argument types.

    > In C, there is no way to take an infinite number of function types in any reliable way to my knowledge.
    I don't know what that means.

    @EVOEx remember the pointer to function isn't cast anywhere. the void* is cast inside *start_routine, if the function needs it.

    but if you wanted a generic pointer to function, you could use any pointer to function type and cast it into the correct type before you call it. people use void(*)() or void(*)(void) because it's nondescript.
    Last edited by robwhit; 08-17-2009 at 12:18 PM.

  14. #14
    Registered User
    Join Date
    Sep 2004
    Location
    California
    Posts
    3,268
    pthread_create is flawed. Or at least it seems so to me.
    It would need to convert that void* to a valid pointer type before calling it anyway, so why not take a correct type from the beginning?
    That void pointer is NOT the thread procedure function that gets called by the pthread implementation. That void pointer is just a user defined argument that can be NULL. Look at the third parameter instead of the fourth; you will see that it indeed takes the correct type of void *(*)(void*).
    bit∙hub [bit-huhb] n. A source and destination for information.

  15. #15
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Ah, I was confused by the Original Post in relation to the discussion. That makes sense, of course.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Following CTools
    By EstateMatt in forum C Programming
    Replies: 5
    Last Post: 06-26-2008, 10:10 AM
  2. Direct3D problem
    By cboard_member in forum Game Programming
    Replies: 10
    Last Post: 04-09-2006, 03:36 AM
  3. Function Pointer help
    By Skydt in forum C Programming
    Replies: 5
    Last Post: 12-02-2005, 09:13 AM
  4. why typedef? and not a pointer to function?
    By terracota in forum Windows Programming
    Replies: 10
    Last Post: 12-19-2004, 06:22 PM
  5. Glib and file manipulation
    By unixOZ in forum Linux Programming
    Replies: 1
    Last Post: 03-22-2004, 09:39 PM