将函数指针转换为另一种类型 [英] Casting a function pointer to another type

查看:32
本文介绍了将函数指针转换为另一种类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有一个接受 void (*)(void*) 函数指针作为回调函数的函数:

Let's say I have a function that accepts a void (*)(void*) function pointer for use as a callback:

void do_stuff(void (*callback_fp)(void*), void* callback_arg);

现在,如果我有这样的功能:

Now, if I have a function like this:

void my_callback_function(struct my_struct* arg);

我可以安全地这样做吗?

Can I do this safely?

do_stuff((void (*)(void*)) &my_callback_function, NULL);

我看过这个问题,我看过一些 C 标准说你可以转换为兼容函数指针",但我找不到兼容函数指针"的定义.

I've looked at this question and I've looked at some C standards which say you can cast to 'compatible function pointers', but I cannot find a definition of what 'compatible function pointer' means.

推荐答案

就 C 标准而言,如果将函数指针强制转换为不同类型的函数指针,然后调用它,则是 未定义的行为.见附件 J.2(资料性):

As far as the C standard is concerned, if you cast a function pointer to a function pointer of a different type and then call that, it is undefined behavior. See Annex J.2 (informative):

在以下情况下行为未定义:

The behavior is undefined in the following circumstances:

  • 指针用于调用类型与指向的函数不兼容的函数类型 (6.3.2.3).

第 6.3.2.3 节第 8 段内容如下:

Section 6.3.2.3, paragraph 8 reads:

指向一种函数的指针可以转换为指向另一种函数的指针再次输入并返回;结果应与原始指针相等.如果一个转换指针用于调用类型与指向类型不兼容的函数,行为未定义.

A pointer to a function of one type may be converted to a pointer to a function of another type and back again; 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 pointed-to type, the behavior is undefined.

所以换句话说,你可以将一个函数指针强制转换为不同的函数指针类型,再将它强制转换回来,然后调用它,一切都会好起来的.

So in other words, you can cast a function pointer to a different function pointer type, cast it back again, and call it, and things will work.

compatible 的定义有些复杂.可在第 6.7.5.3 节第 15 段中找到:

The definition of compatible is somewhat complicated. It can be found in section 6.7.5.3, paragraph 15:

要使两种函数类型兼容,都应指定兼容的返回类型127.

For two function types to be compatible, both shall specify compatible return types127.

此外,参数类型列表,如果两者都存在,应在数量上一致参数和省略号终止符的使用;对应的参数应该有兼容类型.如果一种类型具有参数类型列表,而另一种类型由不属于函数定义并且包含空的函数声明符标识符列表,参数列表不应有省略号终止符和每个的类型参数应与应用程序产生的类型兼容默认参数促销.如果一种类型有参数类型列表,而另一种类型是由包含(可能为空)标识符列表的函数定义指定,两者都应参数个数一致,每个原型参数的类型为与应用默认参数所产生的类型兼容促销到相应标识符的类型.(在确定类型兼容性和复合类型,每个参数用函数或数组声明type 被视为具有调整后的类型,并且每个参数都声明为限定类型被视为具有其声明类型的非限定版本.)

Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types. If one type has a parameter type list and the other type is specified by a function declarator that is not part of a function definition and that contains an empty identifier list, the parameter list shall not have an ellipsis terminator and the type of each parameter shall be compatible with the type that results from the application of the default argument promotions. If one type has a parameter type list and the other type is specified by a function definition that contains a (possibly empty) identifier list, both shall agree in the number of parameters, and the type of each prototype parameter shall be compatible with the type that results from the application of the default argument promotions to the type of the corresponding identifier. (In the determination of type compatibility and of a composite type, each parameter declared with function or array type is taken as having the adjusted type and each parameter declared with qualified type is taken as having the unqualified version of its declared type.)

127) 如果两个函数类型都是旧样式",则不比较参数类型.

127) If both function types are ‘‘old style’’, parameter types are not compared.

判断两种类型是否兼容的规则在6.2.7节有描述,由于篇幅较长,这里不再赘述,你可以在C99 标准草案 (PDF).

The rules for determining whether two types are compatible are described in section 6.2.7, and I won't quote them here since they're rather lengthy, but you can read them on the draft of the C99 standard (PDF).

此处的相关规则在第 6.7.5.1 节第 2 段:

The relevant rule here is in section 6.7.5.1, paragraph 2:

要使两个指针类型兼容,两者都应具有相同的限定,并且都应是指向兼容类型的指针.

For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.

因此,由于 void* struct my_struct* 不兼容void (*)(void*) 类型的函数指针与函数不兼容void (*)(struct my_struct*) 类型的指针,因此函数指针的这种转换在技术上是未定义的行为.

Hence, since a void* is not compatible with a struct my_struct*, a function pointer of type void (*)(void*) is not compatible with a function pointer of type void (*)(struct my_struct*), so this casting of function pointers is technically undefined behavior.

但实际上,在某些情况下,您可以安全地避免强制转换函数指针.在 x86 调用约定中,参数被压入堆栈,并且所有指针的大小相同(x86 中为 4 个字节或 x86_64 中为 8 个字节).调用函数指针归结为将参数压入堆栈并间接跳转到函数指针目标,并且在机器代码级别显然没有类型的概念.

In practice, though, you can safely get away with casting function pointers in some cases. In the x86 calling convention, arguments are pushed on the stack, and all pointers are the same size (4 bytes in x86 or 8 bytes in x86_64). Calling a function pointer boils down to pushing the arguments on the stack and doing an indirect jump to the function pointer target, and there's obviously no notion of types at the machine code level.

你绝对不能做的事情:

  • 在不同调用约定的函数指针之间进行转换.你会弄乱堆栈,最好的情况是崩溃,最坏的情况是,通过一个巨大的安全漏洞默默地成功.在 Windows 编程中,您经常传递函数指针.Win32 期望所有回调函数都使用 stdcall 调用约定(宏 CALLBACKPASCALWINAPI扩展到).如果传递使用标准 C 调用约定 (cdecl) 的函数指针,则会导致错误.
  • 在 C++ 中,在类成员函数指针和常规函数指针之间进行转换.这经常会绊倒 C++ 新手.类成员函数有一个隐藏的this 参数,如果你把一个成员函数转换成一个普通函数,就没有this 对象可以使用,同样,会产生很多不好的结果.
  • Cast between function pointers of different calling conventions. You will mess up the stack and at best, crash, at worst, succeed silently with a huge gaping security hole. In Windows programming, you often pass function pointers around. Win32 expects all callback functions to use the stdcall calling convention (which the macros CALLBACK, PASCAL, and WINAPI all expand to). If you pass a function pointer that uses the standard C calling convention (cdecl), badness will result.
  • In C++, cast between class member function pointers and regular function pointers. This often trips up C++ newbies. Class member functions have a hidden this parameter, and if you cast a member function to a regular function, there's no this object to use, and again, much badness will result.

另一个有时可能有效但也是未定义行为的坏主意:

Another bad idea that might sometimes work but is also undefined behavior:

  • 在函数指针和常规指针之间进行转换(例如,将 void (*)(void) 转换为 void*).函数指针的大小不一定与常规指针相同,因为在某些体系结构上,它们可能包含额外的上下文信息.这可能会在 x86 上正常工作,但请记住这是未定义的行为.
  • Casting between function pointers and regular pointers (e.g. casting a void (*)(void) to a void*). Function pointers aren't necessarily the same size as regular pointers, since on some architectures they might contain extra contextual information. This will probably work ok on x86, but remember that it's undefined behavior.

这篇关于将函数指针转换为另一种类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆