在c中模拟部分function应用

我正在编写一个C程序,如果已经成为该语言的一部分,将从部分function应用程序中受益。 我需要提供一个带有固定签名的函数指针,但有时我需要调用已经编写的需要参数的函数。

我可以编写很多小函数,互相称为:

// 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)

简短的回答是,不,C不支持它。

你可以破解一个可变前端,它接受一个函数的地址并创建一个堆栈帧来用这些参数调用该函数,但它不是可移植的1 。 如果你想让它变得可移植, 并且可以自由地修改你将要通过这个前端调用的其他函数(转换为一个不适合直接调用的forms),你可以重写所有这些函数以接收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; sf = 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_copyva_copy通常只存储一个指向参数开头的指针,该指针只在当前函数返回之前保持有效。 对于va_make_copy ,您必须存储所有实际参数,以便稍后检索它们。 假设您使用了下面概述的argument结构,您将使用va_arg遍历参数,并使用malloc (或其他)为每个参数分配一个节点,并创建某种动态数据结构(例如,链表,动态数组)保存参数,直到你准备好使用它们。

一旦完成特定的绑定function,您还必须添加一些代码来处理释放内存。 它实际上也会改变你的函数签名直接采用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)); 

显然,这可以做到。 不幸的是,它仍然没有好处 – 减少代码的最初目标几乎肯定已完全丢失。 这确实消除了包装器函数 – 但是使用简单的包装器和简单的调用而不是简单的函数,我们最终得到一个复杂的函数和复杂的调用,以及一小段额外的代码将值转换为我们的特殊参数类型,另一个从参数中检索一个值。

虽然有些情况可以certificate这样的代码是合理的(例如,为像Lisp这样的东西编写解释器),但我认为在这种情况下它会导致净损失。 即使我们忽略了添加/使用类型检查的额外代码,每个函数中的代码来检索参数而不是直接接收它们的代码比我们试图替换的包装器更多。


  1. “不可移动”几乎是轻描淡写 – 你真的不能 C语言做到这一点 – 你只需要使用汇编语言来开始。

我不知道有什么直接的方法可以做你想要的,因为C不支持闭包或任何其他方式将“隐藏参数”传递给你的函数(不使用全局变量或静态变量,strtok样式)

你似乎来自FP背景。 在C中,一切都必须手工完成,我冒险说在这种情况下,尝试用OO风格模拟事物可能会更好:通过让函数接收额外的参数,指针将函数转换为“方法”到一个“内在属性”的结构, thisself 。 (如果你碰巧知道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; }