我可以在没有完整签名的情况下声明一个 "opaque" 函数指针类型吗?

Can I declare an "opaque" function pointer type without the full signature?

我可以在没有完整原型的情况下声明一个 opaque 函数指针类型,并初始化一个包含指向该类型函数的指针的结构,而不充实签名(这将在使用时完整声明)。

例如:

// file api.h

typedef void (*function_ptr)(???);

typedef struct {
  function_ptr func;
} S;

function_ptr foo;
function_ptr bar;

const S structs[] = { {foo}, {bar} };

// file impl.c

typedef ret_type (*function_ptr)( // complex argument list );

void foo( // complex argument list) {
...
}

void bar( // complex argument list) {
...
}

structs[0].func(arg1, arg2, arg3);

基本上我想从 api.h 头文件中隐藏函数原型的细节(return 类型和参数列表),但仍然声明使用的 structs指向此函数的指针。在声明任何此类函数或使用它们的位置,完整的原型应该在范围内,并且会进行参数检查等。

类似的模式适用于前向声明的结构:您可以前向声明一个不透明的 struct C 并在其他结构中使用指向该结构的指针,包括指向 C 的指针的静态初始化,例如:

struct C;

typedef struct {
    struct C* c;
} D;

extern struct C c;

D d[] = {
        { &c }
};

C 的详细信息完全隐藏。我正在寻找函数指针的等效模式。

在 C 中,typedef

typedef void (*function_ptr)();

将类型 void (*) ()function_ptr 相关联。类型 void (*) () 表示 "a pointer to a function that takes an unspecified number of arguments and returns void," 并且您可以使用任意数量的参数调用这样的函数指针。这可能符合您的要求。

想不出这在 C 中可能的任何有意义的方式。(在 C++ 中有一些可能满足用例的解决方案。)主要原因是知道某个东西是函数指针并没有任何用处没有任何具体信息。 struct 有一个名称,需要遵守 ODR 规则,所以有一个类型比较的概念,除了名称之外没有其他细节。

不透明的结构指针技术最适合用来隐藏结构内部的所有内容,使文件外部的内容不可见。因此,将函数指针放在结构中可能是最好的选择。这可能会强制执行额外的间接级别。

一个丑陋的解决方法是在你的api.h 文件,然后在调用时将它们转换为实际类型。然后,您需要公开指向 api.h 文件的实际函数指针。您不能使用该函数本身,因为它实际上不是正确的类型(如果您在一个地方声明您的函数 voidfunc 并在其他地方使用它们的完整定义,它可能会起作用,但这是 ODR 违规)。

一种方法是使用双重间接寻址:在函数实现可见的文件中,将函数指针分配给 voidfunc 全局变量。

这是一个草图:

// file api.h
typedef void (*voidfunc_ptr)(void);

typedef {
    voidfunc_ptr* f;
} S;

extern voidfunc_ptr foo_p;
extern voidfunc_ptr bar_p;

S array_of_s[] = { {&foo_p}, {&bar_p} };

// file impl.h

typedef int (*real_function_ptr)( //complex signature... );

// file impl.c which defines foo and bar
int foo( //complex signature... ) { ... }
int bar( //complex signature... ) { ... }

voidfunc_ptr foo_p = (voidfunc_ptr)foo;
voidfunc_ptr bar_p = (voidfunc_ptr)bar;

// some file that uses impl.h and knows about real_function_ptr

void callS(S s) {
  ((real_function_ptr)(*s.f))(// pass complex args); 
}

void use_array() {
  callS(array_of_s[0]);
}

所以你在 api.h 文件中完全隐藏了函数指针的类型,但是一个成本是双重间接寻址(你必须存储一个 voidfunc_ptr* 这是一个指向 -函数指针)以允许从 extern voidfunc_ptr 进行静态初始化。如果你不需要静态初始化,你可以避免双重间接寻址。

另一个成本是丑陋的调用方:您需要将 voidfunc_ptr 转换回真正的底层指针类型。也许有一种方法可以通过声明另一个 structS2 这样双关语 S 来包含正确的指针类型来转移痛苦(我不确定这是否合法)。如果您忘记执行此转换,您可以将函数作为 voidfunc_ptr 调用,可能会导致灾难性的结果。

好处是您仍然可以在调用站点(转换后)进行参数类型检查。