简体   繁体   中英

Is it safe to cast a function pointer to another function pointer in C?

Is it safe to cast these 2 functions to callback pointer type then call them without casting back?

typedef void (*callback)(int i, ...);
    
void foo() {
    // something.
}

void foo2(int i, int j) {
    // something.
}
    
int main(void)
{
    callback c = (callback)&foo, c2 = (callback)&foo2;
    (*c)(0); (*c2)(0,1); 
    return 0;
}

The cast itself is safe, but you must use the correct type when calling the function. This is what the C standard 6.3.2.3 says:

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 referenced type, the behavior is undefined.

In your case void (*)(int i, ...) isn't compatible with either of the other functions, so this code is wildly undefined behavior. However, some compilers provide non-standard extensions for generic function pointer use with the non-prototype void foo() style. But that one in turn is obsolete C and shouldn't be used for that reason - always use void foo (void) in C and never empty parenthesis.

Is it safe to cast these 2 functions to callback pointer type then call them without casting back?

No. The types of the two functions are not compatible with type callback , in the language specification's sense of "compatible", therefore calling either of those functions via a pointer of type callback invokes undefined behavior. Overall, non-variadic function types are never compatible with variadic ones, and in practice, many implementations use different calling conventions for one type than for the other, such that there is no plausible reason even to hope that calling a function of one variety as if it were of the other variety would have the desired effect in any consistent way.

You have several alternatives, among them:

  • Use different callback types for different purposes, each appropriate to its intended callback interface. This way you can avoid casting the callback functions at all. This would be my recommendation. It achieves the best type safety, and you need somehow to keep track of what the actual callback type is anyway, so that you can call it correctly.

  • Use a union of function pointer types. Callback specifiers assign to the appropriate member of the union, and callback callers select the appropriate member.

     typedef union { int (*unary)(int i); int (*binary)(int i, int j); } callback; //... callback cb1 = {.unary = foo }; callback cb2 = {.binary = foo2 }; cb1.unary(1); cb2.binary(1, 2);

    You might even use a tagged union -- one that additionally carries information about which member is used. That would be a bit more complicated to use, but it would give you a means to achieve additional type safety. One of the variations on this approach would be my fallback recommendation if you need a single data type with which multiple callback types can be conveyed.

  • Choose a single callback type that meets all your needs. One way to do that would be to give it a parameter of type void * , by which callback functions can accept any number and type of inputs by, for example, a pointer to a suitable structure type.

     typedef int (*callback)(void *); struct one_int { int i1; }; struct two_int { int i1, i2; }; int foo(void *args) { struct one_int *one_int = args; //... } int foo2(void *args) { struct two_int *two_int = args; //... }
  • Choose any function type as callback . Cast to that type going in, and back to the original type for calls.

  • Specify the callback type without a prototype. In C, if a function declaration that is not part of a definition of that function does not specify a parameter type list then that means that no information is provided about the parameters (unlike in C++, where that means that the function has no parameters). That is compatible with functions requiring any specific number of arguments -- but not variadic ones -- provided that applying the default argument promotions to the parameter types yields compatible types. Type int is a fine parameter type in that regard. The main ones that would be a problem are integer types narrower than int , plus float .

     typedef int (*callback)();

    This would allow exactly the usage you describe for the particular function types in your example.

     callback cb1 = foo; callback cb2 = foo2; (*cb1)(1); // or just cb1(1) (*cb2)(1, 2); // or just cb2(1, 2)

    Contrary to another answer's claim, support for this approach does not constitute an extension to any version of the C language specification published to date. Supporting it is a requirement for conformance with any of C89, C99, C11, and C17. However, it has been declared "obsolescent" in C17, which constitutes a warning that it may be removed from some future version of the language specification. I expect that it indeed will be removed, possibly as soon as the next version of the specification, though obsolescence does not guarantee that.

No, the pointer will still point to the 'old' function (without parameters) and if you call the function, you will put variables on the stack which will never be used.

More problematic will be, if you have a pointer to the function with parameters and cast it to the function without parameters. Then, parameters will be fetched from the stack which you never put there. It is pure chance which values the program will operate on.

Note: This is the most likely behavior, but compilers are not bound to implement it this way (due to undefined behavior).

I guess you know this, but the safer way to do this sort of thing (although it's somewhat of a nuisance) is with explicit casts:

(*(void (*)(int, int))c2)(0, 1); 

Here we:

  1. take the generic "callback" function pointer c2
  2. cast it to the correct function pointer type void (*)(int, int)
  3. call it with the correct arguments

More problematic is your first callback, c . You're trying to call it with one integer argument 0 , but you defined foo as accepting 0 arguments. So a more correct callback to foo as defined would be

(*(void (*)(void))c)();

Or if foo was supposed to take one argument of type int , that would be

(*(void (*)(int))c)(0);

And although as I said, the extra casts here can be a nuisance, this exercise illustrates their benefit: I didn't notice the mismatch between foo 's definition and your callback c , until the compiler warned me about it after I inserted what I thought were the correct casts.

Without the casts, as other answers have explained, the code is unlikely to work. In particular, the attempt to make the callback type "variadic" (that is, with the ... in the prototype) does not help, and may very well hurt. These days, the calling conventions for variadic functions tend to be different from those for ordinary functions (a distinction which the ANSI/ISO C Standard has made since the beginning, although this marked a departure from K&R C). So if the compiler thinks that c2 points to a function of type void (*)(int, ...) , and you call it with (0, 2) , the compiler may very well use a different calling convention (the "varargs" one), and it might not be compatible with the non-varargs foo2 , which is actually of type void (*)(int, int) .

The bottom line is that it is safe to cast (convert) between function pointer types, but you must do it "on both ends"; that is, you must convert back to the correct type before calling. And "the correct type" must match both the actual function being called, and the actual arguments being passed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM