c - 基于函数原型的调用参数编译时决策
问题描述
经常在使用 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
但我没有运气:/
解决方案
您可以在标准 C 中使用_Generic
:
// Define sample functions.
int foo(int a, int b) { return a+b; }
int bar(int a, int b, int c) { return a+b+c; }
// Define names for the old and new types.
typedef int (*Type0)(int, int);
typedef int (*Type1)(int, int, int );
/* Given an identifier (f) for a function and the arguments (a and b) we
want to pass to it, the MyFunctio macro below uses _Generic to test which
type the function is and call it with appropriate arguments.
However, the unchosen items in the _Generic would contain function calls
with mismatched arguments. Even though these are never evaluated, they
violate translation-time contraints for function calls. One solution would
be to cast the function (automatically converted to a function pointer) to
the type being used in the call expression. However, GCC sees through this
and complains the function is called through a non-compatible type, even
though it never actually is. To solve this, the Sanitize macro is used.
The Sanitize macro is given a function type and a function. It uses a
_Generic to select either the function (when it matches the type) or a null
pointer of the type (when the function does not match the type). (And GCC,
although unhappy with a call to a function with wrong parameters, is happy
with a call to a null pointer.)
*/
#define Sanitize(Type, f) \
_Generic((f), Type: (f), default: (Type) 0)
#define MyFunction(f, a, b) \
_Generic(f, \
Type0: Sanitize(Type0, (f)) ((a), (b)), \
Type1: Sanitize(Type1, (f)) ((a), (b), 0) \
)
#include <stdio.h>
int main(void)
{
printf("%d\n", MyFunction(foo, 3, 4));
printf("%d\n", MyFunction(bar, 3, 4));
}
上面使用参数化函数名称 ( f
) 来允许我们用定义的函数 (foo
和bar
) 来演示这一点。对于问题中的情况,函数定义只有一个,名字也只有一个,所以我们可以简化宏。我们也可以使用函数名作为宏名。(在预处理过程中它不会递归替换,因为 C 不会递归替换宏,并且因为名称没有出现在替换文本中,并且后面有左括号。)这看起来像:
#define Sanitize(Type, f) \
_Generic((f), Type: (f), default: (Type) 0)
#define foo(a, b) \
_Generic(foo, \
Type0: Sanitize(Type0, foo) ((a), (b)), \
Type1: Sanitize(Type1, foo) ((a), (b), 0) \
)
…
printf("%d\n", foo(3, 4));
推荐阅读
- python - 你如何循环通过 discord.py 中的公会的所有成员
- c++ - C ++ - 如何找到n叉树的给定节点的深度
- php - 调用未定义的方法 Illuminate\Database\Eloquent\Relations\HasMany::associate()
- spring - Spring Data JDBC Firebird方言无法识别
- r - R:如何将变量名转换为字符串
- google-bigquery - Google bigquery 数据传输服务标头问题
- python - 无法获取推文 tweepy
- python - Pandas 数据框结合了唯一的行值
- go - 将 func 类型转换为其他 func 类型
- django - 如何从 django 模板内的多对多字段关系中选择特定对象