Casting a function pointer to another type
Asked Answered
S

9

121

Let's say I have a function that accepts a void (*)(void*) function pointer for use as a callback:

void do_stuff(void (*callback_fp)(void*), void* callback_arg);

Now, if I have a function like this:

void my_callback_function(struct my_struct* arg);

Can I do this safely?

do_stuff((void (*)(void*)) &my_callback_function, NULL);

I've looked at this question and I've looked at some C standards which say you can cast to "compatible function pointers", but I cannot find a definition of what "compatible function pointer" means.

Sparteine answered 18/2, 2009 at 2:3 Comment(0)
S
144

As far as the C standard is concerned, if you cast a function pointer to a function pointer of a different type and then call that, it is undefined behavior. See Annex J.2 (informative):

The behavior is undefined in the following circumstances:

  • A pointer is used to call a function whose type is not compatible with the pointed-to type (6.3.2.3).

Section 6.3.2.3, paragraph 8 reads:

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.

So in other words, you can cast a function pointer to a different function pointer type, cast it back again, and call it, and things will work.

The definition of compatible is somewhat complicated. It can be found in section 6.7.5.3, paragraph 15:

For two function types to be compatible, both shall specify compatible return types127.

Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types. If one type has a parameter type list and the other type is specified by a function declarator that is not part of a function definition and that contains an empty identifier list, the parameter list shall not have an ellipsis terminator and the type of each parameter shall be compatible with the type that results from the application of the default argument promotions. If one type has a parameter type list and the other type is specified by a function definition that contains a (possibly empty) identifier list, both shall agree in the number of parameters, and the type of each prototype parameter shall be compatible with the type that results from the application of the default argument promotions to the type of the corresponding identifier. (In the determination of type compatibility and of a composite type, each parameter declared with function or array type is taken as having the adjusted type and each parameter declared with qualified type is taken as having the unqualified version of its declared type.)

127) If both function types are ‘‘old style’’, parameter types are not compared.

The rules for determining whether two types are compatible are described in section 6.2.7, and I won't quote them here since they're rather lengthy, but you can read them on the draft of the C99 standard (PDF).

The relevant rule here is in section 6.7.5.1, paragraph 2:

For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.

Hence, since a void* is not compatible with a struct my_struct*, a function pointer of type void (*)(void*) is not compatible with a function pointer of type void (*)(struct my_struct*), so this casting of function pointers is technically undefined behavior.

In practice, though, you can safely get away with casting function pointers in some cases. In the x86 calling convention, arguments are pushed on the stack, and all pointers are the same size (4 bytes in x86 or 8 bytes in x86_64). Calling a function pointer boils down to pushing the arguments on the stack and doing an indirect jump to the function pointer target, and there's obviously no notion of types at the machine code level.

Things you definitely can't do:

  • Cast between function pointers of different calling conventions. You will mess up the stack and at best, crash, at worst, succeed silently with a huge gaping security hole. In Windows programming, you often pass function pointers around. Win32 expects all callback functions to use the stdcall calling convention (which the macros CALLBACK, PASCAL, and WINAPI all expand to). If you pass a function pointer that uses the standard C calling convention (cdecl), badness will result.
  • In C++, cast between class member function pointers and regular function pointers. This often trips up C++ newbies. Class member functions have a hidden this parameter, and if you cast a member function to a regular function, there's no this object to use, and again, much badness will result.

Another bad idea that might sometimes work but is also undefined behavior:

  • Casting between function pointers and regular pointers (e.g. casting a void (*)(void) to a void*). Function pointers aren't necessarily the same size as regular pointers, since on some architectures they might contain extra contextual information. This will probably work ok on x86, but remember that it's undefined behavior.
Shalne answered 18/2, 2009 at 2:54 Comment(12)
As the question-asker pointed out, this only disallows an "incompatible type." It seems like a function type taking a void* should be compatible with a function type taking any other kind of pointer in C, but the question is what the standard says.Pye
@adam, i see you also did your research :) chuck, void* and his struct type* are not compatible. so it's undefined behavior to call it. but anyway, undefined behavior is not really always all that bad. if the compiler does it fine, why worry about it. but in this case, there exist a clean solution.Pitre
Isn't the whole point of void* is that they are compatible with any other pointer? There should be no problem casting a struct my_struct* to a void*, in fact you shouldn't even have to cast, the compiler should just accept it. For instance, if you pass a struct my_struct* to a function that takes a void*, no casting required. What am I missing here that makes these incompatible?Whorl
This answer makes reference to "This will probably work ok on x86...": Are there any platforms where this will NOT work? Does anyone have experience when this failed? qsort() for C seems like a nice place to cast a function pointer if possible.Cassidycassie
@KCArpe: According to the chart under the heading "Implementations of Member Function Pointers" in this article, the 16-bit OpenWatcom compiler sometimes uses a larger function pointer type (4 bytes) than data pointer type (2 bytes) in certain configurations. However, POSIX-conforming systems must use the same representation for void* as for function pointer types, see the spec.Shalne
The link from @adam now refers to the 2016 edition of the POSIX standard where the relevant section 2.12.3 has been removed. You can still find it in the 2008 edition.Rosin
@Whorl No, void * is only "compatible with" any other (non-function) pointer in very precisely defined ways (which are unrelated to what the C standard means with the word "compatible" in this case). C allows a void * to be bigger or smaller than a struct my_struct *, or to have the bits in different order or negated or whatever. So void f(void *) and void f(struct my_struct *) can be ABI-incompatible. C will convert the pointers themselves for you if needed, but it won't and sometimes couldn't convert a pointed-to function to take a possibly different argument type.Zanezaneski
I don't think this analysis is correct. A function taking a void* will work if given any char-aligned pointer. The caller provides a struct-aligned pointer. That's compatible. The caller provides a function requiring a struct-aligned pointer, which is then called with the struct-aligned pointer. The alignment requirements are all satisfied so I don't think there are any isssues. I think you're just getting mixed up by the covariance/contravariance logic of function parameters, which roughly matches "compatibility" you've outlined.Prenatal
@naasking: A system where addresses identify e.g. 16-bit chunks of storage, but which has instructions that can read or write the upper or lower half of such a word, might use one word to hold an int*, but two words to hold a char* or void*. If such a system were to specify that all structures must be word aligned even if they contain only char objects, then a struct anything* could be represented using a single word.Julide
@supercat, I'm not sure if that can happen, because a pointer to a structure must be equivalent to a pointer to its first member, per: port70.net/~nsz/c/c11/n1570.html#6.7.2.1p15Prenatal
@naasking: On a typical 32-bit machine, if one has e.g. struct foo {char a[4]; uint32_t b;} *p = malloc(16),*q;, then one could safely cast the address of p->a[0] to struct foo, but that doesn't mean one could cast the address of p->a[1] likewise. If one had an object p within a large allocation and wanted to copy it to move it up a byte, attempting struct foo temp = *p; *q=temp; would likely be processed as a pair of 32-bit load operations followed by a pair of 32-bit stores. On platforms that don't support unaligned accesses, such an attempt would fail if p's address were...Julide
...not a multiple of four. In fact, even assigning an odd address to q would be UB, a fact which would allow clang to optimize memcpy(*q, &temp, sizeof temp); into a pair of 32-bit loads and stores even though all defined uses of memcpy behave as though processed a byte at a time.Julide
C
42

I asked about this exact same issue regarding some code in GLib recently. (GLib is a core library for the GNOME project and written in C.) I was told the entire slots'n'signals framework depends upon it.

Throughout the code, there are numerous instances of casting from type (1) to (2):

  1. typedef int (*CompareFunc) (const void *a, const void *b)
  2. typedef int (*CompareDataFunc) (const void *b, const void *b, void *user_data)

It is common to chain-thru with calls like this:

int stuff_equal (GStuff      *a,
                 GStuff      *b,
                 CompareFunc  compare_func)
{
    return stuff_equal_with_data(a, b, (CompareDataFunc) compare_func, NULL);
}

int stuff_equal_with_data (GStuff          *a,
                           GStuff          *b,
                           CompareDataFunc  compare_func,
                           void            *user_data)
{
    int result;
    /* do some work here */
    result = compare_func (data1, data2, user_data);
    return result;
}

See for yourself here in g_array_sort(): http://git.gnome.org/browse/glib/tree/glib/garray.c

The answers above are detailed and likely correct -- if you sit on the standards committee. Adam and Johannes deserve credit for their well-researched responses. However, out in the wild, you will find this code works just fine. Controversial? Yes. Consider this: GLib compiles/works/tests on a large number of platforms (Linux/Solaris/Windows/OS X) with a wide variety of compilers/linkers/kernel loaders (GCC/CLang/MSVC). Standards be damned, I guess.

I spent some time thinking about these answers. Here is my conclusion:

  1. If you are writing a callback library, this might be OK. Caveat emptor -- use at your own risk.
  2. Else, don't do it.

Thinking deeper after writing this response, I would not be surprised if the code for C compilers uses this same trick. And since (most/all?) modern C compilers are bootstrapped, this would imply the trick is safe.

A more important question to research: Can someone find a platform/compiler/linker/loader where this trick does not work? Major brownie points for that one. I bet there are some embedded processors/systems that don't like it. However, for desktop computing (and probably mobile/tablet), this trick probably still works.

Cassidycassie answered 26/12, 2012 at 17:56 Comment(3)
A place where it definitely doesn't work is the Emscripten LLVM to Javascript compiler. See github.com/kripken/emscripten/wiki/Asm-pointer-casts for details.Klystron
Upated reference about the Emscripten.Cloying
The link @BenLings posted will break in the near future. It has officially moved to kripken.github.io/emscripten-site/docs/porting/guidelines/…Fredericton
S
12

The point really isn't whether you can. The trivial solution is

void my_callback_function(struct my_struct* arg);
void my_callback_helper(void* pv)
{
    my_callback_function((struct my_struct*)pv);
}
do_stuff(&my_callback_helper);

A good compiler will only generate code for my_callback_helper if it's really needed, in which case you'd be glad it did.

Sanchez answered 18/2, 2009 at 13:27 Comment(5)
The problem is this isn't a general solution. It needs to be done on a case by case basis with knowledge of the function. If you already have a function of the wrong type, you are stuck.Bacon
All compilers I tested this with will generate code for my_callback_helper, unless it is always inlined. This is definitely not necessary, as the only thing it tends to do is jmp my_callback_function. The compiler probably wants to make sure the addresses for the functions are different, but unfortunately it does this even when the function is marked with C99 inline (i.e. "don't care about the address").Intosh
I'm not sure this is correct. Another comment from another reply above (by @mtraceur) says that a void * can be even of different size than a struct * (I think that's wrong, because otherwise malloc would be broken, but that comment has 5 upvotes, so I'm giving it some credit. If @Zanezaneski is right, the solution you wrote wouldn't be correct.Purington
@cesss: It doesn't matter at all if the size is different. The conversion to and from void* still has to work. In short, void* may have more bits, but if you cast a struct* to void* those extra bits can be zeroes and the cast back can just discard those zeroes again.Sanchez
@MSalters: I really didn't know a void * could (in theory) be so different from a struct *. I'm implementing a vtable in C, and I'm using a C++-ish this pointer as the first argument to virtual functions. Obviously, this must be a pointer to the "current" (derived) struct. So, virtual functions need different prototypes depending on the struct they are implemented in. I thought using a void *this argument would fix everything but now I learnt it's undefined behaviour...Purington
P
8

You have a compatible function type if the return type and parameter types are compatible - basically (it's more complicated in reality :)). Compatibility is the same as "same type" just more lax to allow to have different types but still have some form of saying "these types are almost the same". In C89, for example, two structs were compatible if they were otherwise identical but just their name was different. C99 seem to have changed that. Quoting from the c rationale document (highly recommended reading, btw!):

Structure, union, or enumeration type declarations in two different translation units do not formally declare the same type, even if the text of these declarations come from the same include file, since the translation units are themselves disjoint. The Standard thus specifies additional compatibility rules for such types, so that if two such declarations are sufficiently similar they are compatible.

That said - yeah strictly this is undefined behavior, because your do_stuff function or someone else will call your function with a function pointer having void* as parameter, but your function has an incompatible parameter. But nevertheless, i expect all compilers to compile and run it without moaning. But you can do cleaner by having another function taking a void* (and registering that as callback function) which will just call your actual function then.

Pitre answered 18/2, 2009 at 3:2 Comment(0)
N
3

As C code compiles to instruction which do not care at all about pointer types, it's quite fine to use the code you mention. You'd run into problems when you'd run do_stuff with your callback function and pointer to something else then my_struct structure as argument.

I hope I can make it clearer by showing what would not work:

int my_number = 14;
do_stuff((void (*)(void*)) &my_callback_function, &my_number);
// my_callback_function will try to access int as struct my_struct
// and go nuts

or...

void another_callback_function(struct my_struct* arg, int arg2) { something }
do_stuff((void (*)(void*)) &another_callback_function, NULL);
// another_callback_function will look for non-existing second argument
// on the stack and go nuts

Basically, you can cast pointers to whatever you like, as long as the data continue to make sense at run-time.

Nahshon answered 18/2, 2009 at 2:19 Comment(0)
P
0

Well, unless I understood the question wrong, you can just cast a function pointer this way.

void print_data(void *data)
{
    // ...
}


((void (*)(char *)) &print_data)("hello");

A cleaner way would be to create a function typedef.

typedef void(*t_print_str)(char *);
((t_print_str) &print_data)("hello");
Precess answered 14/8, 2022 at 20:50 Comment(0)
T
0

It's undefined behaviour as some other answers/comments have explained.

But it would be well-defined if you replace void my_callback_function(struct my_struct* arg) with

void my_callback_function(void *arg)
{
   struct my_struct* p = arg;
   ... // use p
}

assuming that you really passed a pointer to a my_struct object as an argument when calling the function by the function pointer.

It is true that a pointer to any object can be cast to void and then cast back to the original type without any change in its value, but this can only guarantee that a function like I suggested above works, not void my_callback_function(struct my_struct* arg) People tend to believe that they are actually the same.

Thiourea answered 19/10, 2023 at 23:30 Comment(0)
S
-1

If you think about the way function calls work in C/C++, they push certain items on the stack, jump to the new code location, execute, then pop the stack on return. If your function pointers describe functions with the same return type and the same number/size of arguments, you should be okay.

Thus, I think you should be able to do so safely.

Sclera answered 18/2, 2009 at 2:21 Comment(2)
you're only safe as long as struct-pointers and void-pointers have compatible bit-representations; that's not guaranteed to be the caseYesseniayester
Compilers also can pass arguments in registers. And it's not unheard of to use different registers for floats, ints or pointers.Sanchez
S
-1

Void pointers are compatible with other types of pointer. It's the backbone of how malloc and the mem functions (memcpy, memcmp) work. Typically, in C (Rather than C++) NULL is a macro defined as ((void *)0).

Look at 6.3.2.3 (Item 1) in C99:

A pointer to void may be converted to or from a pointer to any incomplete or object type

Stumble answered 19/1, 2011 at 14:36 Comment(2)
This contradicts to Adam Rosenfield's answer, see the last paragraph and commentsIntendant
This answer is clearly wrong. Any pointer is convertible to a void pointer, except for function pointers.Kelly

© 2022 - 2024 — McMap. All rights reserved.