Home > database >  Conversion of function pointers
Conversion of function pointers

Time:06-10

I was wondering if we can create a generic function pointer, which would point to a function with any return type in C. I tried it through this code:

typedef void (*func_ptr)(int, int);

int func1(int a, int b) {
    return a * b;
}
int func2(int a, int b) {
    return a   b;
}

int main() {
    func_ptr ptr1, ptr2;
    ptr1 = func1;
    ptr2 = func2;
  
    printf("Func1: %d\n", *(int *)(*ptr1)(4, 5));
    printf("Func2: %d\n", *(int *)(*ptr2)(4, 5));
    return 0;
}

If the func_ptr was of type int, the code would work. But what is wrong with this approach? Why can't we assign void * pointer to integer functions?

CodePudding user response:

If the func_ptr was of type int, the code would work

More precisely: if the func_ptr type definition specified a return type of int, the initialization code would be correct, but the invocation would still be invalid because the *(int *) cannot be applied to the void return value and invoking the function with a prototype different from its calling convention has undefined behavior anyway. func_ptr should be cast as ((int(*)(int, int))func_ptr) for the call to be correct.

There is no such thing as a generic function pointer, but a function taking no arguments and returning void is a close as can be. Functions with different prototypes can be stored into incompatible function pointers as long as you use a proper cast and the pointer must be cast back to the exact prototype of the function it points to at the call sites. This is possible for C, but more tricky for C .

Beware that function call is a postfix operation, so it binds stronger than prefix operations such as a cast: (int(*)(int, int))func_ptr(3, 4) does not work. You must write:

((int(*)(int, int))func_ptr)(3, 4)

Here is a modified version:

#include <stdio.h>

// define a generic function pointer of no arguments with no return value
typedef void (*func_ptr)(void);

// actual function type: function of taking 2 ints, returning an int
typedef int (*func_of_int_int_retuning_int)(int, int);


int func1(int a, int b) {
    return a * b;
}
int func2(int a, int b) {
    return a   b;
}

int main() {
    func_ptr ptr1, ptr2;
    // use explicit casts for assignment (with or without the typedef)
    ptr1 = (func_ptr)func1;
    ptr2 = (void (*)(void))func2;
  
    // at call sites, the function pointer must be cast back to the actual
    // type for invocation with arguments and handling of return value
    // again you can use a typedef or not
    printf("Func1: %d\n", ((int (*)(int, int))ptr1)(4, 5));
    printf("Func2: %d\n", ((func_of_int_int_retuning_int)ptr2)(4, 5));
    return 0;
}

CodePudding user response:

You are allowed to convert between different function pointers, but you are not allowed to call a function pointer unless it's compatible with the pointed-to type. You can force the assignment to compile by using a cast (which will get you a warning), but as soon as you try to call ptr1 or ptr2 you get Undefined Behavior.

You can call them if you first convert them back to the correct function type. This is correct:

typedef void (*func_ptr) (int, int); // literally any function type would work

typedef int (*real_func_ptr) (int, int);

int func1(int a, int b) { return a * b; }
int func2(int a, int b) { return a   b; }

int main() {
  func_ptr ptr1, ptr2;
  ptr1 = (func_ptr) func1;
  ptr2 = (func_ptr) func2;

  int a = ((real_func_ptr)ptr1)(4, 5);
  int b = ((real_func_ptr)ptr2)(4, 5);

  return a   b;
}

A few more points about your post:

  • I see this a lot, where students say "void function" and "int function", as if the function type is its return type. This is wrong and very misleading. The function type is: "function taking two arguments of type int and returning int" (aka int (int, int))

  • your typedef is: pointer to function taking 2 int arguments and returning void. It's not returning void *.

CodePudding user response:

There is no generic function pointer type similar to void* for object pointers. However, the C standard actually guarantees that we can convert between any two function pointers and back, as long as we call the function through the correct kind of function pointer. This means that any function pointer type can actually be used as a generic function pointer type.

There are many problems on these lines: *(int *)(*ptr1)(4, 5).

  • You may not call ptr1 since it is of type void(*)(int,int) but it points at a function of type int(*))(int, int). You need to cast back to the correct type before calling. (A separate variable like an enum might be used to keep track of which type the pointer actually points at.)
  • Either way the *(int *) cast would just cast the result after the function call, so it doesn't do what you want.

Generic programming with function pointers generally involves deciding a certain function type to use as template, then implement all functions accordingly.

Or we can use _Generic inside a macro to sort out the types at compile-time, like for example this:

#define func(x,y) \
  _Generic((x), int:   _Generic((y), int:   int_func,    default: 0), \
                char*: _Generic((y), char*: string_func, default: 0) ) (x,y)

int main (void) 
{
  printf("Func1: %d\n", func(4,5));
  printf("Func2: %s\n", func("hello","world"));
}
  • Related