我正在编写一个C程序,如果已经成为语言的一部分,将从部分函数应用程序中受益。我需要提供一个带有固定签名的函数指针,但有时我需要调用已经编写的需要参数的函数。
我可以编写很多小函数,互相称为:
// example, not actual code
void (*atJoystickLeft)(void); // fixed signature
.. code ..
atJoystickLeft = &func1;
.. code ..
void func1(void) { func2(10, "a string"); } /
我想改为:
atJoystickLeft = &func2(10, "a string");
所以我可以处理函数func1,它只是为调用func2设置参数。
这会使代码至少减少20% - 30%。我的简单问题是:有人在C中模拟部分函数应用程序吗? (C99)
答案 0 :(得分:7)
简短的回答是,不,C不支持它。
你可以将一个可变参数的前端组合在一起,该前端接受一个函数的地址并创建一个堆栈帧来用这些参数调用该函数,但它不是可移植的 1 。如果你想让它可移植,和可以自由修改你要通过这个前端调用的其他函数(转换成一个不适合直接调用的形式)你可以重写所有这些函数接收va_alist
作为唯一参数,并使用va_arg
检索正确数量/类型的参数:
// pointer to function taking a va_alist and return an int:
typedef int (*func)(va_alist);
void invoke(func, ...) {
va_alist args;
va_start(args, func);
func(args);
va_end(args);
}
编辑:对不起,正如@missingno指出的那样,我并没有按照预期的方式完成这项工作。这应该是两个函数:一个接受输入并将它们包装到一个结构中,另一个接受结构并调用预期的函数。
struct saved_invocation {
func f;
argument_list args;
};
saved_invocation save(func f, ...) {
saved_invocation s;
va_alist args;
s.f = f;
va_start(args, f);
va_make_copy(s.args, args);
va_end(args);
}
int invoke(saved_invocation const *s) {
s->f(s->args);
}
对于va_make_copy
,你会遇到更多非标准的东西。它与va_copy
不同 - va_copy
通常只存储一个指向参数开头的指针,该指针只在当前函数返回之前保持有效。对于va_make_copy
,您必须存储所有实际参数,以便稍后检索它们。假设您使用了我在下面概述的argument
结构,您将使用va_arg
遍历参数,并使用malloc
(或其他)为每个参数分配一个节点,并创建一些一种动态数据结构(例如,链表,动态数组)来保存参数,直到你准备好使用它们为止。
在完成特定绑定功能后,您还必须添加一些代码来处理释放内存的问题。它实际上也会改变你的函数签名直接采用va_list,采用你设计的任何类型的数据结构来保存你的参数列表。
[结束编辑]
这意味着您要调用的每个其他函数的签名都需要:
int function(va_alist args);
...然后这些函数中的每一个都必须通过va_arg
检索它的参数,所以(例如)一个函数将以两个整数作为其参数,并返回它们的总和看起来像什么像这样:
int adder(va_alist args) {
int arg1 = va_arg(args, int);
int arg2 = va_arg(args, int);
return arg1 + arg2;
}
这有两个明显的问题:首先,即使我们不再需要为每个函数使用单独的包装器,我们仍然会为每个函数添加额外的代码,以便通过单个包装器调用它。在代码大小方面,它不太可能比收支平衡好得多,并且可能很容易成为净损失。
然而,更糟糕的是,因为我们现在将所有函数的所有参数检索为变量参数列表,所以我们不再对参数进行任何类型检查。如果我们想要的话,它(当然)可以添加一个小的包装器类型和代码来处理它:
struct argument {
enum type {CHAR, SHORT, INT, LONG, UCHAR, USHORT, UINT, ULONG, /* ... */ };
union data {
char char_data;
short short_data;
int int_data;
long long_data;
/* ... */
}
}
然后,当然,您还要编写更多代码来检查每个参数的枚举是否表明它是预期的类型,并从联合中检索正确的数据(如果是)。然而,这会给调用函数添加一些严重的丑陋 - 而不是:
invoke(func, arg1, arg2);
......你会得到类似的东西:
invoke(func, make_int_arg(arg1), make_long_arg(arg2));
显然,可以完成。不幸的是,它仍然没有好处 - 减少代码的最初目标几乎肯定已完全丢失。这确实消除了包装函数 - 但是使用简单的包装器和简单的调用而不是简单的函数,我们最终得到一个复杂的函数和复杂的调用,以及一小段额外的代码将值转换为我们的特殊参数类型,另一个从参数中检索一个值。
虽然有些情况可以证明这样的代码是合理的(例如,为像Lisp这样的代码编写解释器),但我认为在这种情况下它会导致净损失。即使我们忽略了添加/使用类型检查的额外代码,每个函数中用于检索参数而不是直接接收它们的代码也不仅仅是我们试图替换的包装器。
答案 1 :(得分:1)
我不知道有什么直接的方法可以做你想要的,因为C不支持闭包或任何其他方式将“隐藏参数”传递给你的函数(不使用全局变量或静态变量,strtok样式)
您似乎来自FP背景。在C中,一切都必须手工完成,我冒险说在这种情况下,尝试用OO风格模拟事物可能更好:通过制作它们将你的功能变成“方法”接收一个额外的参数,一个指向“内部属性”结构的指针,this
或self
。 (如果您碰巧知道Python这应该非常明确:))
在这种情况下,例如,func1
可能会变为这样:
typedef struct {
int code;
char * name;
} Joystick;
Joystick* makeJoystick(int code, char* name){
//The constructor
Joystick* j = malloc(sizeof Joystick);
j->code = code;
j->name = name;
return j;
}
void freeJoystick(Joystick* self){
//Ever noticed how most languages with closures/objects
// have garbage collection? :)
}
void func1(Joystick* self){
func2(self->code, self->name);
}
int main(){
Joystick* joyleft = make_Joystick(10, "a string");
Joystick* joyright = make_Joystick(17, "b string");
func1(joyleft);
func1(joyright);
freeJoystick(joyleft);
freeJoystick(joyright);
return 0;
}