猿问

将函数指针转换为另一种类型

将函数指针转换为另一种类型

假设我有一个函数可以接受void (*)(void*)函数指针,用作回调:

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

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

void my_callback_function(struct my_struct* arg);

我能安全地做这件事吗?

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

我看过这个问题我看了一些C标准,这些标准说你可以转换成“兼容函数指针”,但是我找不到“兼容函数指针”的定义。


弑天下
浏览 564回答 3
3回答

翻过高山走不出你

就C标准而言,如果将函数指针转换为不同类型的函数指针,然后调用它,则为未定义行为..见附件J.2(资料):在下列情况下未对行为进行定义:指针用于调用其类型与指向类型不兼容的函数(6.3.2.3)。第6.3.2.3节第8段内容如下:指向一种类型的函数的指针可以转换为指向另一种类型的函数的指针,然后再转换回来;结果应该与原始指针相比较。如果转换后的指针用于调用其类型与指向类型不兼容的函数,则该行为是未定义的。换句话说,您可以将函数指针转换为不同的函数指针类型,然后再将其转换回函数指针,然后调用它,这样就可以工作了。对…的定义兼容有点复杂。见第6.7.5.3节第15段:对于两个函数类型是兼容的,两者都应该指定兼容的返回类型。127.此外,参数类型列表(如果两者都存在)应在参数的数量和使用省略号终止符方面达成一致;相应的参数应具有兼容的类型。如果一个类型有一个参数类型列表,而另一个类型是由一个函数声明器指定的,它不是函数定义的一部分,并且包含一个空的标识符列表,则该参数列表不应该有一个省略终止符,并且每个参数的类型都应该与应用默认参数提升所产生的类型兼容。如果一种类型具有参数类型列表,而另一种类型是由包含(可能为空)标识符列表的函数定义指定的,则两者应在参数数量上达成一致,并且每个原型参数的类型应与应用默认参数提升到相应标识符类型所产生的类型兼容。(在确定类型兼容性和组合类型时,以函数或数组类型声明的每个参数被视为具有已调整类型,而以限定类型声明的每个参数被视为具有其声明类型的不合格版本)。127)如果两种函数类型都是“旧样式”,则不对参数类型进行比较。确定两种类型是否兼容的规则在第6.2.7节中有说明,我在这里不引用它们,因为它们很长,但是您可以在C99标准草案(PDF).此处的有关规则载于第6.7.5.1节第2段:如果两种指针类型是兼容的,两者都应该是相同限定的,并且两者都应该是指向兼容类型的指针。因此,因为void* 不兼容带着struct my_struct*类型的函数指针void (*)(void*)与类型的函数指针不兼容。void (*)(struct my_struct*)因此,函数指针的这种转换在技术上是未定义的行为。但实际上,在某些情况下,您可以安全地使用转换函数指针。在x86调用约定中,参数被推送到堆栈上,所有指针都是相同大小的(x86中的4个字节或x86_64中的8个字节)。调用函数指针可以归结为推送堆栈上的参数,并间接跳转到函数指针目标,显然在机器代码级别上没有类型的概念。你肯定不能做:在不同调用约定的函数指针之间进行转换。你会把堆栈搞砸,充其量,崩溃,最坏的情况是,在一个巨大的安全漏洞中默默地成功。在Windows编程中,您经常传递函数指针。Win 32要求所有回调函数使用stdcall调用约定(其中宏CALLBACK, PASCAL,和WINAPI全部扩展到)。如果传递使用标准C调用约定的函数指针(cdecl),坏就会发生。在C+中,在类成员函数指针和规则函数指针之间进行转换。这经常会引起C+新手的注意。类成员函数有一个隐藏的this参数,如果将成员函数强制转换为常规函数,则不存在this对象,同样会产生许多不好的结果。另一个错误的想法,有时可能有效,但也是未定义的行为:函数指针和常规指针之间的转换(例如,将void (*)(void)转到void*)。函数指针不一定与常规指针大小相同,因为在某些体系结构中,它们可能包含额外的上下文信息。这可能会在x86上正常工作,但请记住,它是未定义的行为。

慕哥9229398

如果返回类型和参数类型是兼容的,则有一个兼容的函数类型-基本上(实际上更复杂:)。兼容性和“相同类型”是一样的,只是允许有不同的类型,但仍然有某种形式的“这些类型几乎相同”。例如,在C89中,如果两个结构在其他方面是相同的,但只是它们的名称不同,那么它们是兼容的。C99似乎改变了这一点。引用C基本原理文件(强烈推荐阅读,顺便说一句!):结构、联合或枚举类型声明在两个不同的翻译单元中不会正式声明相同的类型,即使这些声明的文本来自相同的包含文件,因为翻译单元本身是不相交的。因此,标准为这类型指定了额外的兼容性规则,以便如果两个此类声明非常相似,则它们是兼容的。严格地说,这是一种未定义的行为,因为您的do_add函数或其他人将用函数指针调用您的函数。void*作为参数,但您的函数有一个不兼容的参数。不过,我希望所有编译器都能在没有抱怨的情况下编译和运行它。但是,您可以通过使用另一个函数来进行清洁。void*(并将其注册为回调函数),然后调用实际函数。
随时随地看视频慕课网APP
我要回答