【问题标题】:Can I declare an "opaque" function pointer type without the full signature?我可以声明一个没有完整签名的“不透明”函数指针类型吗?
【发布时间】:2017-12-02 01:35:42
【问题描述】:

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

例如:

// 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 头文件中隐藏函数原型的细节(返回类型和参数列表),但仍然声明使用指向该函数的指针的structs。在任何此类函数的声明位置或它们的使用位置,完整的原型应该在范围内,并且将进行参数检查等。

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

struct C;

typedef struct {
    struct C* c;
} D;

extern struct C c;

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

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

【问题讨论】:

  • 函数槽的什么属性需要在文件外可见?只是它占用了结构中的空间,并且您希望它在已知实际类型的文件中进行类型检查?
  • @ZalmanStern - 正确。所有函数指针都可以转换为所有其他指针,因此不应该有任何类型的“改变指针大小”行为需要处理。
  • const S structs[] = { {foo}, {bar} }; 是一个错误:静态变量的初始化程序必须是常量表达式。此外,它还会导致在标题中出现多重定义错误。
  • @M.M - 对,事实上定义实际上是在.c 文件中。是的,{foo} 东西不起作用,但考虑到我想做的那种文件的伪代码,因为我不知道正确的语法(如果foo 是一个函数,它肯定会起作用,例如.
  • @ZalmanStern - 我认为您不能安全地将函数指针转换为 void * 并再次返回。指向函数的指针和指向其他所有内容的指针不一定具有相同的表示或大小。也就是说,我提出了一个解决方案 below - 但它是任意函数指针类型(void (*)(void) 以避免该问题。

标签: c function-pointers


【解决方案1】:

在 C 中,类型定义

typedef void (*function_ptr)();

void (*) () 类型与function_ptr 相关联。 void (*) () 类型的意思是“一个指向函数的指针,它接受一个未指定数量的参数并返回void”,你可以用你想要的任意数量的参数调用这样的函数指针像。这可能符合您的要求。

【讨论】:

  • 我在上面的示例中使用void 表示我的“我不知道如何声明这个”,尽管该类型实际上不是void(我也想隐藏它) .我想在这个函数的实际调用站点进行类型检查,我想我不会得到这个?
  • @BeeOnRope 是的,如果您以这种方式处理事情,您将失去它。也许你最好隐藏整个结构?
【解决方案2】:

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

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

【讨论】:

  • 上述答案指出您可以利用 pre-ANSI C 无原型行为。我不认为这有多大帮助,但它确实存在。
  • 被接受为“不透明结构”似乎在这里提供了最好的方法(它似乎比我使用指向函数指针的指针的答案要好得多,因为两者都涉及间接,这是更干净。如果你已经有一个可以隐藏指针的前向声明结构,有时它可能是免费的。
【解决方案3】:

一个丑陋的解决方法是在 api.h 文件中声明 any 有效函数指针类型的指针(例如,最简单的 void 返回 0-arg 函数,如 void (*voidfunc_ptr)(void)),然后强制转换它们调用时的实际类型。然后,您需要将实际函数指针公开到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 转换回真正的底层指针类型。也许有一种方法可以通过声明另一个struct(如S2)来将痛苦转移到其他地方,双关语S 包含正确的指针类型(我不确定这是否合法)。如果您忘记执行此转换,您可以将函数调用为 voidfunc_ptr,这可能会导致灾难性的结果。

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

【讨论】:

  • C 规范是否保证将函数指针类型相互转换是安全的?我似乎记得在 C++ 中这是未定义的行为,我怀疑 C 可能也不喜欢那样。
  • @templatetypedef - 是的,例如参见this answer。本质上,您可以将 any 函数指针作为任何其他函数指针传递或存储,但在调用它时它必须具有正确的类型。因此,对于其他类型的指针,任何函数指针都提供与 void * 类似的功能。
猜你喜欢
  • 2013-07-12
  • 2013-11-22
  • 2019-03-23
  • 1970-01-01
  • 2020-04-30
  • 2019-05-06
  • 1970-01-01
  • 2019-11-09
  • 1970-01-01
相关资源
最近更新 更多