将这 2 个函数转换为回调指针类型然后调用它们而不回转换是否安全?
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;
}
Run Code Online (Sandbox Code Playgroud)
强制转换本身是安全的,但在调用函数时必须使用正确的类型。这是 C 标准 6.3.2.3 所说的:
指向一种类型函数的指针可以转换为指向另一种类型函数的指针,然后再返回;结果应与原始指针相等。如果使用转换后的指针调用类型与引用类型不兼容的函数,则行为未定义。
在您的情况下void (*)(int i, ...),与其他任何一个函数都不兼容,因此此代码是非常未定义的行为。然而,一些编译器为非原型void foo()风格的泛型函数指针使用提供了非标准的扩展。但是那个反过来又是过时的 C 并且不应该因为这个原因使用 - 总是void foo (void)在 C 中使用并且永远不要空括号。
将这两个函数转换为回调指针类型然后调用它们而不转换回来是否安全?
不。这两个函数的类型与 type 不兼容callback,在语言规范的“兼容”意义上,因此通过 type 指针调用这两个函数中的任何一个callback都会调用未定义的行为。总体而言,非可变参数函数类型永远不会与可变参数函数类型兼容,并且在实践中,许多实现对一种类型使用与另一种类型不同的调用约定,因此甚至没有合理的理由希望将一种类型的函数调用为如果它是其他品种,将以任何一致的方式达到预期的效果。
您有多种选择,其中:
将不同的回调类型用于不同的目的,每种类型都适合其预期的回调接口。这样您就可以完全避免强制转换回调函数。 这是我的建议。 它实现了最好的类型安全,并且您需要以某种方式跟踪实际的回调类型是什么,以便您可以正确调用它。
使用函数指针类型的联合。回调说明符分配给联合的适当成员,回调调用者选择适当的成员。
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);
Run Code Online (Sandbox Code Playgroud)
您甚至可以使用标记联合——它还包含有关使用哪个成员的信息。这使用起来会有点复杂,但它会给你一种实现额外类型安全的方法。 如果您需要可以传递多个回调类型的单一数据类型,则此方法的变体之一是我的后备建议。
选择满足您所有需求的单一回调类型。一种方法是为其提供一个 type 参数void *,回调函数可以通过该参数接受任意数量和类型的输入,例如指向合适结构类型的指针。
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; // ...
}
Run Code Online (Sandbox Code Playgroud)
选择任意函数类型作为callback。转换为进入的类型,然后返回到原始类型以进行调用。
指定没有原型的回调类型。在 C 中,如果不属于该函数定义一部分的函数声明未指定参数类型列表,则意味着未提供有关参数的信息(与 C++ 不同,这意味着该函数没有参数) 。这与需要任何特定数量的参数的函数兼容(但不是可变参数),只要将默认参数提升应用于参数类型会产生兼容的类型。int从这方面来说,Type是一个很好的参数类型。主要的问题是比int, plus窄的整数类型float。
typedef int (*callback)();
Run Code Online (Sandbox Code Playgroud)
这将完全允许您在示例中描述的特定函数类型的用法。
callback cb1 = foo;
callback cb2 = foo2;
(*cb1)(1); // or just cb1(1)
(*cb2)(1, 2); // or just cb2(1, 2)
Run Code Online (Sandbox Code Playgroud)
与另一个答案的说法相反,对这种方法的支持并不构成对迄今为止发布的任何版本的 C 语言规范的扩展。支持它是符合 C89、C99、C11 和 C17 中任何一个的要求。然而,它在 C17 中已被声明为“过时”,这构成了一个警告,即它可能会从语言规范的某些未来版本中删除。我预计它确实会被删除,可能会在规范的下一个版本中删除,尽管过时并不能保证这一点。