C中的任意函数指针使用哪种类型?

fgw7neuy  于 2023-02-11  发布在  其他
关注(0)|答案(2)|浏览(174)

简而言之,我的问题是:C标准是否允许类似于void *的任意函数指针类型作为任意数据指针类型?
通常使用void *参数定义回调函数类型,以传递回调函数知道格式但调用方不知道格式的任意数据包。
例如:

typedef void (* EventFunctionType)(void *data);

    void RegisterEventFunction(EventFunctionType function, void *data);

然后,"EventFunction"可以注册一个数据指针,该指针将在调用函数时传递给函数。
现在假设我们想要传递一个函数指针给回调函数,这个函数可以有任何一个原型,这个原型对于特定的回调函数来说是已知的,就像上面的任意数据结构一样。
一个void *cannot hold a function pointer,那么可以使用哪种类型?
注意:这个问题的一个明显的解决方案是用正确的函数指针类型将函数指针 Package 在一个数据结构中,但问题是如果函数指针可以直接以泛型形式传递,那么回调函数可以将其转换为具有正确原型的指针吗?

bhmjp9jg

bhmjp9jg1#

不存在与void指针工作方式相同/相似的函数指针类型。
但是函数指针还有另外一个可以利用的特性,这个问题的答案中已经提到了:
在C11标准草案N1570中,www.example.com § 8:6.3.2.3 §8:
指向一种类型的函数的指针可以转换为指向另一种类型的函数的指针,然后再转换回来。
这意味着您可以使用任何函数指针类型作为您的"任意函数指针类型"。只要您知道如何返回到实数/原始类型(即,知道原始类型以便您可以正确转换),这并不重要。
例如:

typedef void (*func_ptr_void)(void);

然后使用func_ptr_void作为"任意函数指针类型"。
但请注意,与void*和其他对象指针类型之间的转换不同,函数指针之间的转换总是需要显式强制转换,下面的代码示例显示了这种差异:

#include <stdio.h>

typedef void (*func_ptr_void)(void);

typedef int (*f_int)(int);

int bar(int n)
{
    return n * n;
}

int test(func_ptr_void f, int y)
{
    f_int fc = (f_int)f; // Explicit cast
    return fc(y);
}

int foo(void* p)
{
    int* pi = p;    // Explicit cast not needed
    return *pi;
}

int main(void)
{
    int x = 42;
    void* pv = &x;  // Explicit cast not needed
    printf("%d \n", foo(pv));
    
    func_ptr_void fpv = (func_ptr_void)bar;   // Explicit cast
    printf("%d \n", test(fpv, 5));
    
    return 0;
}
8i9zcol2

8i9zcol22#

C标准是否允许类似void * 的任意函数指针类型作为任意数据指针类型?
不可以。两个函数指针只有在返回类型和参数(包括限定符)匹配的情况下才兼容。
然而,通过强制类型转换在任意两个函数指针之间进行的指针转换是定义良好的(6.3.2.3/8),只要你不通过错误的指针类型调用函数。这意味着你可以使用任何函数指针类型作为“泛型函数指针”,只要你跟踪指针实际指向的函数。例如为此使用额外的枚举。
通常在使用函数指针时,我们不这样做,而是定义一个公共接口,例如bsearch/qsort的回调使用int (*)(const void*, const void*)
下面是一个“使用枚举跟踪类型”的例子,我并不特别推荐它,但它的定义非常好:

#include <stdio.h>

static int intfunc (int x)
{
  printf("%d\n", x);
  return x;
}

static double doublefunc (double x)
{
  printf("%f\n", x);
  return x;
}

typedef enum
{
  INTFUNC,
  DOUBLEFUNC
} functype_t;

typedef void generic_func_t (void);
typedef int  int_func_t     (int);
typedef int  double_func_t  (double);

typedef struct
{
  generic_func_t* fptr;
  functype_t type;
} func_t;

void func_call (const func_t* f)
{
  switch(f->type)
  {
    case INTFUNC:    ((int_func_t*)f->fptr )   (1);   break;
    case DOUBLEFUNC: ((double_func_t*)f->fptr) (1.0); break;
  }
}

int main (void)
{
  func_t f1 = { (generic_func_t*)intfunc, INTFUNC };
  func_t f2 = { (generic_func_t*)doublefunc, DOUBLEFUNC };

  func_call(&f1);
  func_call(&f2);
}

这是“老派”的C语言,但不推荐使用,因为它笨拙、脆弱,而且不是真正的类型安全。在现代C编程中,我们不会写这种代码,而是用下面这样的代码来替换整个混乱:

#include <stdio.h>

static int intfunc (int x)
{
  printf("%d\n", x);
  return x;
}

static double doublefunc (double x)
{
  printf("%f\n", x);
  return x;
}

#define func_call(obj)               \
  _Generic((obj),                    \
           int:    intfunc,          \
           double: doublefunc) (obj) \
                        

int main (void)
{
  func_call(1);
  func_call(1.0);
}

相关问题