经常在使用 3rd 方库时,我发现自己需要编写胶水“代码”来处理跨版本更改的函数原型。
以 Linux 内核为例:这是一个常见的场景——假设我们有函数int my_function(int param),在某些时候我们需要添加一个可选的void *data. 不会破坏 API,而是以这种方式添加新参数:
int __my_function(int param, void *data);
static inline my_function(int param) {
return __my_function(param, NULL);
}
这很好:它对那些不需要它的人隐藏了新参数和 API 损坏。现有代码可以继续my_function与旧原型一起使用。
然而,情况并非总是如此(无论是在 Linux 中还是在其他库中),我最终得到了这样的部分,以处理我遇到的所有可能的版本:
#if LIBRARY_VERSION > 5
my_function(1, 2, 3);
#elif LIBRARY_VERSION > 4
my_function(1, 2);
#else
my_function(1);
#endif
所以我在想,对于简单的原型更改(参数的重新排序,“默认”参数的添加/删除等),让编译器自动完成它会很好。
我希望自动完成此操作(无需指定确切的版本),因为有时要查明引入更改的确切库/内核版本是过多的工作。因此,如果我不关心新参数并且可以使用 eg 0,我希望编译器在0需要时使用。
我得到的最远的是:
#include <stdio.h>
#if 1
int f(int a, int b) {
return a + b;
}
#else
int f(int a) {
return a + 5;
}
#endif
int main(void) {
int ret = 0;
int (*p)() = (int(*)())f;
if (__builtin_types_compatible_p(typeof(f), int(int, int)))
ret = p(3, 4);
else if (__builtin_types_compatible_p(typeof(f), int(int)))
ret = p(1);
else
printf("no matching call\n");
printf("ret: %d\n", ret);
return 0;
}
这有效 - GCC 在编译类型中选择适当的调用,但它有 2 个问题:
- 通过“无类型”函数指针调用,我们失去了类型检查。合法
p("a", "b")并给出垃圾结果。 - 似乎没有发生标准类型提升(再次,可能是因为我们通过无类型指针进行调用)
(我可能在这里遗漏了其他问题,但这些是我认为最关键的点)
我相信它可以通过一些宏魔法来实现:如果调用参数是分开的,宏可以生成找到合适原型的代码,然后逐一测试所有给定参数的类型兼容性。但我认为使用起来会复杂得多,所以我正在寻找一个更简单的解决方案。
有什么想法可以通过适当的类型检查+促销来实现吗?我认为如果不使用编译器扩展就无法完成,所以我的问题集中在针对 Linux 的现代 GCC。
编辑:在宏化它+为演员添加橡子的想法之后,我只剩下这个了:
#define START_COMPAT_CALL(f, ret_type, params, args, ret_value) if (__builtin_types_compatible_p(typeof(f), ret_type params)) (ret_value) = ( ( ret_type(*) params ) (f) ) args
#define ELSE_COMPAT_CALL(f, ret_type, params, args, ret_value) else START_COMPAT_CALL(f, ret_type, params, args, ret_value)
#define END_COMPAT_CALL() else printf("no matching call!\n")
int main(void) {
int ret = 0;
START_COMPAT_CALL(f, int, (int, int), (999, 9999), ret);
ELSE_COMPAT_CALL(f, int, (int), (1), ret);
ELSE_COMPAT_CALL(f, int, (char, char), (5, 9999), ret);
ELSE_COMPAT_CALL(f, int, (float, int), (3, 5), ret);
ELSE_COMPAT_CALL(f, int, (float, char), (3, 5), ret);
END_COMPAT_CALL();
printf("ret: %d\n", ret);
return 0;
}
这适用于我注意到的两点 - 它提供类型检查警告并正确执行提升。但它也会对所有“未选择”的呼叫站点发出警告:warning: function called through a non-compatible type. 我尝试用它来结束通话,__builtin_choose_expr但我没有运气:/