我使用函数指针的结构来实现不同后端的接口.签名非常不同,但返回值几乎都是void,void*或int.
struct my_interface { void (*func_a)(int i); void *(*func_b)(const char *bla); ... int (*func_z)(char foo); };
但是后端不需要支持每个接口函数的功能.所以我有两种可能性,第一种选择是在每次调用之前检查指针是否为NULL.我不太喜欢这样,因为可读性和因为我担心性能影响(但我没有测量它).另一种选择是具有虚函数,对于极少数情况,接口函数不存在.
因此,我需要为每个签名都使用一个虚函数,我想知道是否有可能只有一个用于不同的返回值.并将其转换为给定的签名.
#includeint nothing(void) {return 0;} typedef int (*cb_t)(int); int main(void) { cb_t func; int i; func = (cb_t) nothing; i = func(1); printf("%d\n", i); return 0; }
我用gcc测试了这段代码,它确实有效.但它是否理智?或者它可以破坏堆栈还是会导致其他问题?
编辑:感谢所有答案,经过一番深入阅读后,我现在学到了很多关于调用约定的知识.并且现在可以更好地理解引擎盖下发生的事情.
根据C规范,转换函数指针会导致未定义的行为.实际上,有一段时间,GCC 4.3预发布版会在你输出一个函数指针时返回NULL,完全有效,但是它们在发布之前就退出了,因为它打破了很多程序.
假设GCC继续执行它现在所做的工作,它将使用默认的x86调用约定(以及大多数架构上的大多数调用约定),但我不会依赖它.在每个调用点测试函数指针对NULL并不比函数调用昂贵得多.如果你真的想要,你可以写一个宏:
#define CALL_MAYBE(func, args...) do {if (func) (func)(## args);} while (0)
或者你可以为每个签名设置不同的虚函数,但我可以理解你想要避免这种情况.
查尔斯贝利打电话给我,所以我去看了细节(而不是依靠我的空洞记忆).在C规范说
766指向一种类型的函数的指针可以转换为指向另一种类型的函数的指针,然后再返回;
767结果应比较等于原始指针.
768如果转换的指针用于调用类型与指向类型不兼容的函数,则行为未定义.
和GCC 4.2预发布(这是4.3之前的解决方案)遵循这些规则:函数指针的强制转换不会导致NULL,正如我所写,但试图通过不兼容的类型调用函数,即
func = (cb_t)nothing; func(1);
从你的例子中,将导致一个abort
.他们改回4.1行为(允许但警告),部分原因是这次改变打破了OpenSSL,但同时修复了OpenSSL,这是未定义的行为,编译器可以随时自由更改.
OpenSSL只是向其他函数类型转换函数指针,并且返回相同数量的相同精确值的值,并且这(假设您不处理浮点数)碰巧在所有平台和调用约定中都是安全的了解.但是,其他任何事情都可能不安全.