简体   繁体   English

在 C 中将 function 指针转换为另一个 function 指针是否安全?

[英]Is it safe to cast a function pointer to another function pointer in C?

Is it safe to cast these 2 functions to callback pointer type then call them without casting back?将这两个函数转换为回调指针类型然后在不回退的情况下调用它们是否安全?

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;
}

The cast itself is safe, but you must use the correct type when calling the function.强制转换本身是安全的,但在调用 function 时必须使用正确的类型。 This is what the C standard 6.3.2.3 says:这就是 C 标准 6.3.2.3 所说的:

A pointer to a function of one type may be converted to a pointer to a function of another type and back again;指向一种类型的 function 的指针可以转换为指向另一种类型的 function 的指针,然后再返回; the result shall compare equal to the original pointer.结果应与原始指针比较。 If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.如果使用转换后的指针调用类型与引用类型不兼容的 function,则行为未定义。

In your case void (*)(int i, ...) isn't compatible with either of the other functions, so this code is wildly undefined behavior.在您的情况下void (*)(int i, ...)与其他任何一个函数都不兼容,因此此代码是非常未定义的行为。 However, some compilers provide non-standard extensions for generic function pointer use with the non-prototype void foo() style.但是,一些编译器为通用 function 指针与非原型void foo()样式一起使用提供了非标准扩展。 But that one in turn is obsolete C and shouldn't be used for that reason - always use void foo (void) in C and never empty parenthesis.但是那个又是过时的 C 并且不应该因此而使用 - 始终在 C 中使用void foo (void)并且永远不要使用空括号。

Is it safe to cast these 2 functions to callback pointer type then call them without casting back?将这两个函数转换为回调指针类型然后在不回退的情况下调用它们是否安全?

No. The types of the two functions are not compatible with type callback , in the language specification's sense of "compatible", therefore calling either of those functions via a pointer of type callback invokes undefined behavior.不。这两个函数的类型与callback类型不兼容,在语言规范的“兼容”意义上,因此通过callback类型的指针调用这些函数中的任何一个都会调用未定义的行为。 Overall, non-variadic function types are never compatible with variadic ones, and in practice, many implementations use different calling conventions for one type than for the other, such that there is no plausible reason even to hope that calling a function of one variety as if it were of the other variety would have the desired effect in any consistent way.总的来说,非可变 function 类型从不与可变参数兼容,并且在实践中,许多实现对一种类型使用不同的调用约定而不是另一种类型,因此甚至没有合理的理由希望将一种类型的 function 调用为如果它是其他品种,它将以任何一致的方式产生预期的效果。

You have several alternatives, among them:您有几种选择,其中包括:

  • Use different callback types for different purposes, each appropriate to its intended callback interface.为不同的目的使用不同的回调类型,每个都适合其预期的回调接口。 This way you can avoid casting the callback functions at all.这样您就可以完全避免强制转换回调函数。 This would be my recommendation.这将是我的建议。 It achieves the best type safety, and you need somehow to keep track of what the actual callback type is anyway, so that you can call it correctly.它实现了最佳的类型安全性,并且您需要以某种方式跟踪实际的回调类型是什么,以便您可以正确调用它。

  • Use a union of function pointer types.使用 function 指针类型的联合。 Callback specifiers assign to the appropriate member of the union, and callback callers select the appropriate member.回调说明符分配给联合体的适当成员,回调调用者 select 是适当的成员。

     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);

    You might even use a tagged union -- one that additionally carries information about which member is used.您甚至可以使用带标签的联合——它还包含有关使用哪个成员的信息。 That would be a bit more complicated to use, but it would give you a means to achieve additional type safety.使用起来会有点复杂,但它会给你一种实现额外类型安全的方法。 One of the variations on this approach would be my fallback recommendation if you need a single data type with which multiple callback types can be conveyed.如果您需要一种可以传达多种回调类型的单一数据类型,那么这种方法的一种变体将是我的后备建议

  • Choose a single callback type that meets all your needs.选择满足您所有需求的单一回调类型。 One way to do that would be to give it a parameter of type void * , by which callback functions can accept any number and type of inputs by, for example, a pointer to a suitable structure 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; //... }
  • Choose any function type as callback .选择任何 function 类型作为callback Cast to that type going in, and back to the original type for calls.转换为该类型进入,然后返回到调用的原始类型。

  • Specify the callback type without a prototype.指定不带原型的回调类型。 In C, if a function declaration that is not part of a definition of that function does not specify a parameter type list then that means that no information is provided about the parameters (unlike in C++, where that means that the function has no parameters). In C, if a function declaration that is not part of a definition of that function does not specify a parameter type list then that means that no information is provided about the parameters (unlike in C++, where that means that the function has no parameters) . That is compatible with functions requiring any specific number of arguments -- but not variadic ones -- provided that applying the default argument promotions to the parameter types yields compatible types.这与需要任何特定数量的 arguments 的函数兼容——但不是可变参数——只要将默认参数提升应用于参数类型会产生兼容的类型。 Type int is a fine parameter type in that regard.在这方面,类型int是一个很好的参数类型。 The main ones that would be a problem are integer types narrower than int , plus float .主要的问题是 integer 类型比int窄,加上float

     typedef int (*callback)();

    This would allow exactly the usage you describe for the particular function types in your example.这将完全允许您在示例中为特定 function 类型描述的用法。

     callback cb1 = foo; callback cb2 = foo2; (*cb1)(1); // or just cb1(1) (*cb2)(1, 2); // or just cb2(1, 2)

    Contrary to another answer's claim, support for this approach does not constitute an extension to any version of the C language specification published to date.与另一个答案的说法相反,对这种方法的支持并不构成对迄今为止发布的 C 语言规范的任何版本的扩展。 Supporting it is a requirement for conformance with any of C89, C99, C11, and C17.支持它是符合 C89、C99、C11 和 C17 中任何一个的要求。 However, it has been declared "obsolescent" in C17, which constitutes a warning that it may be removed from some future version of the language specification.但是,它在 C17 中已被宣布为“过时”,这构成了一个警告,即它可能会从某些未来版本的语言规范中删除。 I expect that it indeed will be removed, possibly as soon as the next version of the specification, though obsolescence does not guarantee that.我希望它确实会被删除,可能会在规范的下一个版本中被删除,尽管过时并不能保证这一点。

No, the pointer will still point to the 'old' function (without parameters) and if you call the function, you will put variables on the stack which will never be used.不,指针仍将指向“旧的”function(不带参数),如果您调用 function,您会将变量放在永远不会使用的堆栈上。

More problematic will be, if you have a pointer to the function with parameters and cast it to the function without parameters.如果您有一个指向带有参数的 function 的指针并将其转换为不带参数的 function ,则问题会更大。 Then, parameters will be fetched from the stack which you never put there.然后,将从您从未放在那里的堆栈中获取参数。 It is pure chance which values the program will operate on.程序运行的价值观纯属偶然。

Note: This is the most likely behavior, but compilers are not bound to implement it this way (due to undefined behavior).注意:这是最可能的行为,但编译器不一定要以这种方式实现它(由于未定义的行为)。

I guess you know this, but the safer way to do this sort of thing (although it's somewhat of a nuisance) is with explicit casts:我想你知道这一点,但是做这种事情的更安全的方法(虽然它有点麻烦)是使用显式强制转换:

(*(void (*)(int, int))c2)(0, 1); 

Here we:在这里,我们:

  1. take the generic "callback" function pointer c2采用通用“回调”function 指针c2
  2. cast it to the correct function pointer type void (*)(int, int)将其转换为正确的 function 指针类型void (*)(int, int)
  3. call it with the correct arguments用正确的 arguments 调用它

More problematic is your first callback, c .更有问题的是您的第一个回调c You're trying to call it with one integer argument 0 , but you defined foo as accepting 0 arguments.您尝试使用一个 integer 参数0来调用它,但您将foo定义为接受 0 arguments。 So a more correct callback to foo as defined would be因此,定义的对foo的更正确回调将是

(*(void (*)(void))c)();

Or if foo was supposed to take one argument of type int , that would be或者如果foo应该接受一个int类型的参数,那将是

(*(void (*)(int))c)(0);

And although as I said, the extra casts here can be a nuisance, this exercise illustrates their benefit: I didn't notice the mismatch between foo 's definition and your callback c , until the compiler warned me about it after I inserted what I thought were the correct casts.虽然正如我所说,这里的额外转换可能会令人讨厌,但这个练习说明了它们的好处:我没有注意到foo的定义和你的回调c之间的不匹配,直到编译器在我插入我的内容后警告我认为是正确的演员表。

Without the casts, as other answers have explained, the code is unlikely to work.正如其他答案所解释的那样,如果没有演员表,代码就不可能工作。 In particular, the attempt to make the callback type "variadic" (that is, with the ... in the prototype) does not help, and may very well hurt.特别是,尝试使callback类型“可变”(即在原型中使用... )没有帮助,而且很可能会受到伤害。 These days, the calling conventions for variadic functions tend to be different from those for ordinary functions (a distinction which the ANSI/ISO C Standard has made since the beginning, although this marked a departure from K&R C).如今,可变参数函数的调用约定往往与普通函数的调用约定不同(ANSI/ISO C 标准从一开始就做出了区分,尽管这标志着与 K&R C 的背离)。 So if the compiler thinks that c2 points to a function of type void (*)(int, ...) , and you call it with (0, 2) , the compiler may very well use a different calling convention (the "varargs" one), and it might not be compatible with the non-varargs foo2 , which is actually of type void (*)(int, int) .因此,如果编译器认为c2指向void (*)(int, ...)类型的 function 并且您使用(0, 2)调用它,编译器很可能会使用不同的调用约定(“varargs " one),并且它可能与非可变参数foo2兼容,后者实际上是void (*)(int, int)类型。

The bottom line is that it is safe to cast (convert) between function pointer types, but you must do it "on both ends";底线是在 function 指针类型之间进行转换(转换)安全的,您必须“在两端”进行; that is, you must convert back to the correct type before calling.也就是说,您必须在调用之前转换回正确的类型。 And "the correct type" must match both the actual function being called, and the actual arguments being passed.并且“正确的类型”必须与调用的实际 function 和传递的实际 arguments 相匹配。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM